【问题标题】:Alternative tuple iteration using constexpr使用 constexpr 的替代元组迭代
【发布时间】:2015-09-17 14:36:54
【问题描述】:

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

但是,当我想使用 constexpr 函数“helper”来实现替代版本时,编译器 (g++ 5.2.0) 会陷入错误消息的无限循环。根据我从这些消息中可以得到的信息,“位置”模板参数被实例化为大得可笑(== 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、...),人们可以使用向量实现类似的版本。但我认为元组版本在概念上更适合我的问题,并且在运行时更快。

【问题讨论】:

  • 如果你知道元组的所有元素都是同一类型,为什么不使用std::array

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


【解决方案1】:

当你有一个函数模板时——所有的代码必须被编译出来。所以在这里:

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);
}

我们总是编译条件的 both 部分(旁注:您的条件是向后的)。所以当position == 0 时,std::get&lt;0&gt;(tuple) 部分被编译 std::get&lt;0&gt;(tuple) + helper&lt;T, -1&gt;(tuple) 部分被编译。但要做到这一点,我们需要编译helper&lt;T, -2&gt;(tuple)。和helper&lt;T, -3&gt;(tuple)。我们无限递归。

您的专业化方法有效,因为 Helper&lt;T, 0&gt; 只适用于 std::get&lt;0&gt;。那里没有其他逻辑,所以我们停下来。如果您想在功能上执行此操作,更简单的方法是将位置作为参数传递。那就是:

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&lt;0&gt; ),我们就成功地终止了递归。


在 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) + ... );
}

【讨论】:

  • @JonathanWakely 我喜欢 SO 的时间戳分辨率不够精确,无法显示我们中的哪一个先发布。
  • 仅供以后的读者阅读:辅助函数的顺序很重要。不能互换 helper(T const& tuple, Pos) 和 helper(T const& tuple, Pos)。
  • 后向条件 (0 == position) 被幽默地称为尤达条件,一些编码人员使用它来避免意外的值分配:return 0 = position ... 是一个明显的错误,而 return position = 0 ... 是(在最好的情况)一个偷偷摸摸的警告。
  • @PaperBirdMaster 这和这个问题有什么关系?
  • @PaperBirdMaster 这也与答案无关。当我说条件是向后的时,我的意思是 OP 翻转了真假表达式。如果position == 0,OP 打算只做std::get&lt;0&gt;(tuple) - 但逻辑是相反的。
【解决方案2】:

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

是的,这是错误的,因为你已经这样做了。可以,但是不能用运行时条件终止编译时递归。​​

当您实例化helper&lt;T, N&gt;(T) 时实例化helper&lt;T, N-1&gt;(T) 实例化herlper&lt;T, N-2&gt;(t) 等等,没有任何东西可以终止递归。

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

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

【讨论】:

    猜你喜欢
    • 2017-08-29
    • 1970-01-01
    • 2014-02-24
    • 2013-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-30
    相关资源
    最近更新 更多