使用constexpr替代元组迭代

时间:2015-09-17 14:36:54

标签: c++ templates c++11 c++14

我想编写一个迭代std::tuple<...>的函数。迭代本身不会产生关于元组模板类型的任何问题,因为'...'具有相同的类型(如int,int,int,...)。我使用模板metapgrogramming实现了一个带有辅助结构'Helper'的工作函数'Foo' - 一切都很好。

但是当我想使用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,...),可以使用向量实现类似的版本。但我认为元组版本在概念上对我的问题更好,在运行时更快。

2 个答案:

答案 0 :(得分:6)

当您有一个功能模板时 - 必须编译所有代码。所以这里:

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 == 0 std::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 :(得分:3)

  

所以我的问题:使用constexpr函数在编译期间尝试这种迭代是否主要是错误的?

是的,这是错的,因为你已经完成了。它可以完成,但是你无法在运行时条件下终止编译时递归。​​

当您实例化helper<T, N>(T)实例化helper<T, N-1>(T)实例化herlper<T, N-2>(t)等等时,没有任何内容可以终止递归。

由于部分特化,类模板版本在到达N==0时终止,但您无法部分专门化函数模板。

Barry的解决方案通过使用第二个重载来终止递归,因此当它到达0时,它会选择一个不同的函数,并且不会永远地实例化相同的函数。