如何在C ++ 11中迭代std :: tuple

时间:2014-11-13 06:27:10

标签: c++ c++11 tuples

我做了以下元组:

我想知道如何迭代它?有tupl_size(),但阅读文档,我没有得到如何利用它。我也搜索了SO,但问题似乎是Boost::tuple

auto some = make_tuple("I am good", 255, 2.1);

3 个答案:

答案 0 :(得分:28)

template<class F, class...Ts, std::size_t...Is>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)func(std::get<Is>(tuple)), 0)... };
}

template<class F, class...Ts>
void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func){
    for_each_in_tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>());
}

用法:

auto some = std::make_tuple("I am good", 255, 2.1);
for_each_in_tuple(some, [](const auto &x) { std::cout << x << std::endl; });

Demo

std::index_sequence和系列是C ++ 14的功能,但它们可以很容易地在C ++ 11中实现(在SO上有很多可用的)。多态lambda也是C ++ 14,但可以用自定义编写的算子代替。

答案 1 :(得分:24)

这是尝试将元组的迭代分解为组件部分。

首先,表示按顺序执行一系列操作的函数。请注意,许多编译器发现这很难理解,尽管它是合法的C ++ 11,据我所知:

template<class... Fs>
void do_in_order( Fs&&... fs ) {
  int unused[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... }
  (void)unused; // blocks warnings
}

接下来,一个函数采用std::tuple,并提取访问每个元素所需的索引。通过这样做,我们可以在以后完善前进。

作为附带好处,我的代码支持std::pairstd::array迭代:

template<class T>
constexpr std::make_index_sequence<std::tuple_size<T>::value>
get_indexes( T const& )
{ return {}; }

肉和土豆:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  do_in_order( [&]{ f( get<Is>(std::forward<Tuple>(tup)) ); }... );
}

和面向公众的界面:

template<class Tuple, class F>
void for_each( Tuple&& tup, F&& f ) {
  auto indexes = get_indexes(tup);
  for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f) );
}

虽然它指出Tuple,但它适用于std::arraystd::pair。它还将所述对象的r / l值类别转发到它调用的函数对象。另请注意,如果您的自定义类型具有免费功能get<N>,并且覆盖get_indexes,则上述for_each将适用于您的自定义类型。

如上所述,do_in_order虽然很多编译器都不支持,但是他们不喜欢将未扩展参数包扩展到参数包中的lambda。

我们可以在这种情况下内联do_in_order

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  int unused[] = { 0, ( (void)f(get<Is>(std::forward<Tuple>(tup)), 0 )... }
  (void)unused; // blocks warnings
}

这并不会花费太多的冗长,但我个人觉得不太清楚。在我看来,do_in_order如何运作的阴影魔术在内联中变得模糊不清。

index_sequence(和支持模板)是一个可以用C ++ 11编写的C ++ 14特性。在堆栈溢出上找到这样的实现很容易。目前最热门的谷歌搜索是a decent O(lg(n)) depth implementation,如果我正确阅读评论可能是实际gcc make_integer_sequence的至少一次迭代的基础(评论还指出了一些进一步的编译时改进)消除sizeof...次来电。)

或者我们可以写:

template<class F, class...Args>
void for_each_arg(F&&f,Args&&...args){
  using discard=int[];
  (void)discard{0,((void)(
    f(std::forward<Args>(args))
  ),0)...};
}

然后:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  for_each_arg(
    std::forward<F>(f),
    get<Is>(std::forward<Tuple>(tup))...
  );
}

这避免了手动扩展,但编译更多的编译器。我们通过Is参数传递auto&&i

在C ++ 1z中,我们也可以使用std::applyfor_each_arg函数对象来取消索引摆弄。

答案 2 :(得分:10)

这是一个类似的,比TC先前接受的更详细的解决方案,它可能更容易理解( - 它可能与网络中的其他数千个相同):

template<typename TupleType, typename FunctionType>
void for_each(TupleType&&, FunctionType
            , std::integral_constant<size_t, std::tuple_size<typename std::remove_reference<TupleType>::type >::value>) {}

template<std::size_t I, typename TupleType, typename FunctionType
       , typename = typename std::enable_if<I!=std::tuple_size<typename std::remove_reference<TupleType>::type>::value>::type >
void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>)
{
    f(std::get<I>(t));
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>());
}

template<typename TupleType, typename FunctionType>
void for_each(TupleType&& t, FunctionType f)
{
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>());
}

用法(使用std::tuple):

auto some = std::make_tuple("I am good", 255, 2.1);
for_each(some, [](const auto &x) { std::cout << x << std::endl; });

用法(使用std::array):

std::array<std::string,2> some2 = {"Also good", "Hello world"};
for_each(some2, [](const auto &x) { std::cout << x << std::endl; });

DEMO


一般想法:,如在T.C.的解决方案中,从索引I=0开始,并达到元组的大小。但是,这里不是每次可变扩展而是一次一个地进行。

<强>解释

  • 如果for_each等于元组的大小,则调用I的第一个重载。然后函数什么都不做,这样就结束了递归。

  • 第二个重载使用参数std::get<I>(t)调用函数,并将索引增加1。需要类std::integral_constant才能在编译时解析I的值。 std::enable_if SFINAE内容用于帮助编译器将此重载与前一个重载分开,并且只有在I小于元组大小时调用此重载(在Coliru上这是需要的,而在Visual中) Studio没有工作。

  • 第三个用I=0启动递归。通常从外部调用过载。




编辑:我还提到了Yakk提到的通过使用通用模板参数std::array来额外支持std::pairTupleType的想法一个专门用于std::tuple<Ts ...>的。

由于TupleType类型需要推断并且是这样的通用引用&#34;,这进一步具有免费获得完美转发的优势。缺点是必须通过typename std::remove_reference<TupleType>::type使用另一个间接,因为TupleType也可能被推断为参考类型。