2015-09-17 78 views
3

我想寫一個函數,這種函數在std::tuple<...>上迭代。迭代本身不會產生關於元組模板類型的任何問題,因爲'...'具有相同的類型(如int,int,int,...)。我使用模板metapgrogramming實現了一個輔助結構'Helper'的工作函數'Foo' - 一切都很好。使用constexpr的替代元組迭代

但是,當我想要使用constexpr函數'helper'來實現替代版本時,編譯器(g ++ 5.2.0)會陷入無限循環的錯誤消息中。從我可以從這些消息中獲得的'position'模板參數被實例化爲可笑的大(== 4294967245)而不是(== 1)。我試圖儘可能使語法和命名儘可能接近。

小例子

#include <tuple> 
// template metaprogramming version 
template 
<class T, std::size_t position> 
struct Helper{ 
    static int 
    help(T tuple) { 
     return std::get<position>(tuple) + 
      Helper<T,position - 1>::help(tuple); 
    } 
}; 

// template metaprogramming version, specialized 
template 
<class T> 
struct Helper<T,0>{ 
    static int 
    help(T tuple) { 
     return std::get<0>(tuple); 
    } 
}; 
// function version, not working 
template 
<class T, std::size_t position> 
constexpr int 
helper(T tuple) { 
    return 
     0 == position ? 
      std::get<position>(tuple) + helper<T,position-1>(tuple) : 
      std::get<0>(tuple); 
} 

template 
<class T> 
auto 
Foo(T tuple) { 
    constexpr std::size_t dimension = std::tuple_size<T>::value; 
    // working version, using the helper struct 
    return Helper<T,dimension - 1>::help(tuple); 
    // wrong(?) version, using the constexpr helper function 
    return helper<T,dimension - 1>(tuple); 
} 

int main() { 
    std::tuple<int,int> t(1,1); 
    Foo(t); 
    return 0; 
} 

我的問題:

  • 是它主要錯誤嘗試這種迭代的過程中編譯 時間constexpr的功能呢?
  • 如果沒有,是編譯器 中的錯誤還是正確的版本應該如何?

我完全知道,由於元組(int,int,...)中的類型相同,所以可以實現與向量類似的版本。但我認爲元組版本在概念上更適合我的問題,並且在運行時更快。

+0

如果您知道元組的所有元素都是同一類型,爲什麼不使用'std :: array'? – mattnewport

回答

4

當你有一個函數模板 - 所有代碼必須被編譯出來。所以在這裏:

template 
<class T, std::size_t position> 
constexpr int helper(T tuple) { 
    return 
     0 == position ? 
      std::get<position>(tuple) + helper<T,position-1>(tuple) : 
      std::get<0>(tuple); 
} 

我們始終編譯部分有條件的(側面說明:您的條件是向後)。所以當position == 0std::get<0>(tuple)部分編譯std::get<0>(tuple) + helper<T, -1>(tuple)部分編譯。但是要做到這一點,我們需要編譯helper<T, -2>(tuple)。和helper<T, -3>(tuple)。我們遞歸無限。

您的專業化方法可行,因爲Helper<T, 0>只是std::get<0>。那裏沒有其他的邏輯,所以我們停下來。如果你想在功能上做到這一點,更簡單的方法是通過作爲參數。那就是:

template <std::size_t position> 
using Pos = std::integral_constant<std::size_t, position>; // to save me some typing 

template <typename T> 
constexpr int helper(T const& tuple, Pos<0>) 
{ 
    return std::get<0>(tuple); 
} 

template <typename T, std::size_t position> 
constexpr int helper(T const& tuple, Pos<position>) 
{ 
    return std::get<position>(tuple) + helper(tuple, Pos<position - 1>{}); 
} 

在這裏,我們通過重載使用的條件 - 所以一旦我們得到helper(T, Pos<0>),我們成功地終止遞歸。


在C++ 1Z,這成爲一噸更容易折表情,在這裏你可以這樣做:

template <typename T> 
constexpr int sum_tuple(T const& tuple) { 
    return sum_tuple(tuple, std::make_index_sequence<std::tuple_size<T>::value>{}); 
} 

template <typename T, std::size_t... Is> 
constexpr int sum_tuple(T const& tuple, std::index_sequence<Is...>) 
{ 
    return (std::get<Is>(tuple) + ...); 
} 
+1

@JonathanWakely我喜歡SO的時間戳分辨率不夠精確,無法顯示我們中的哪一個首先發布。 – Barry

+0

只爲後來的讀者:輔助函數的順序很重要。不能交換幫助者(T const&tuple,Pos )和幫助者(T const&tuple,Pos <0>)。 – claudio

+1

有條件的('0 ==位置')被幽默地引用爲Yoda條件,並被某些編碼器用來避免意外的值賦值:'return 0 = position ...'是一個明顯的錯誤,而'return position = 0 ...'(最好的情況下)是一個偷偷摸摸的警告。 –

3

所以我的問題:在編譯時使用constexpr函數嘗試這種迭代是否是主要錯誤?

是的,這是錯誤的,因爲你已經做了。它可以完成,但不能用運行時條件終止編譯時遞歸。

當你實例化helper<T, N>(T)時,它實例化了helper<T, N-1>(T),它實例化了herlper<T, N-2>(t)等等,沒有任何東西可以終止遞歸。

由於部分專業化,類模板版本在達到N==0時終止,但不能部分專門化功能模板。

Barry的解決方案通過使用第二個重載來終止遞歸,以便當它到達0時,它選擇一個不同的函數並且不會永久實例化同一個函數。