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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26902633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:41:32  来源:igfitidea点击:

How to iterate over a std::tuple in C++ 11

c++c++11tuples

提问by Mostafa Talebi

I have made the following tuple:

我做了以下元组:

I want to know how should I iterate over it? There is tupl_size(), but reading the documentation, I didn't get how to utilize it. Also I have search SO, but questions seem to be around Boost::tuple.

我想知道我应该如何迭代它?有tupl_size(),但是阅读文档后,我不知道如何使用它。我也有搜索,但问题似乎围绕着Boost::tuple

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

回答by T.C.

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

Usage:

用法:

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_sequenceand family are C++14 features, but they can be easily implemented in C++11 (there are many available on SO). Polymorphic lambdas are also C++14, but can be replaced with a custom-written functor.

std::index_sequence和 family 是 C++14 特性,但它们可以在 C++11 中轻松实现(SO 上有很多可用)。多态 lambda 也是 C++14,但可以用自定义编写的函子替换。

回答by Yakk - Adam Nevraumont

Here is an attempt to break down iterating over a tuple into component parts.

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

First, a function that represents doing a sequence of operations in order. Note that many compilers find this hard to understand, despite it being legal C++11 as far as I can tell:

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

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

Next, a function that takes a std::tuple, and extracts the indexes required to access each element. By doing so, we can perfect forward later on.

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

As a side benefit, my code supports std::pairand std::arrayiteration:

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

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

The meat and potatoes:

肉和土豆:

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

and the public-facing interface:

和面向公众的界面:

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

while it states Tupleit works on std::arrays and std::pairs. It also forward the r/l value category of said object down to the function object it invokes. Also note that if you have a free function get<N>on your custom type, and you override get_indexes, the above for_eachwill work on your custom type.

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

As noted, do_in_orderwhile neat isn't supported by many compilers, as they don't like the lambda with unexpanded parameter packs being expanded into parameter packs.

如前所述,do_in_order虽然许多编译器不支持整洁,因为他们不喜欢将未扩展的参数包扩展为参数包的 lambda。

We can inline do_in_orderin that case

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
}

this doesn't cost much verbosity, but I personally find it less clear. The shadow magic of how do_in_orderworks is obscured by doing it inline in my opinion.

这不会花费太多冗长,但我个人觉得它不太清楚。do_in_order在我看来,通过内联进行,隐藏了工作原理的阴影魔法。

index_sequence(and supporting templates) is a C++14 feature that can be written in C++11. Finding such an implementation on stack overflow is easy. A current top google hit is a decent O(lg(n)) depth implementation, which if I read the comments correctly may be the basis for at least one iteration of the actual gcc make_integer_sequence(the comments also point out some further compile-time improvements surrounding eliminating sizeof...calls).

index_sequence(和支持模板)是可以用 C++11 编写的 C++14 特性。在堆栈溢出时找到这样的实现很容易。当前最热门的 google 命中是一个体面的 O(lg(n)) 深度实现,如果我正确阅读评论,这可能是实际 gcc 至少一次迭代的基础make_integer_sequence(评论还指出了一些进一步的编译时改进周围消除sizeof...呼叫)。

Alternatively we can write:

或者我们可以写:

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

And then:

进而:

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

Which avoids the manual expand yet compiles on more compilers. We pass the Isvia the auto&&iparameter.

这避免了手动扩展,但可以在更多编译器上编译。我们通过Is通过auto&&i参数。

In C++1z we can also use std::applywith a for_each_argfunction object to do away with the index fiddling.

在C ++ 1Z我们还可以使用std::applyfor_each_arg函数对象废除指数摆弄。

回答by davidhigh

Here is a similar and more verbose solution than the formerly accepted one given by T.C., which is maybe a little bit easier to understand (-- it's probably the same as thousand others out there in the net):

这是一个类似且比 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>(std::forward<TupleType>(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>());
}

Usage (with std::tuple):

用法(与std::tuple):

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

Usage (with std::array):

用法(与std::array):

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

DEMO

演示



General idea:as in the solution of T.C., start with an index I=0and go up to the size of the tuple. However, here it is done not per variadic expansion but one-at-a-time.

大体思路:和TC的解决方案一样,从一个索引开始,I=0一直到元组的大小。然而,这里不是按可变参数扩展而是一次一个。

Explanation:

解释:

  • The first overload of for_eachis called if Iis equal to the size of the tuple. The function then simply does nothing and such end the recursion.

  • The second overload calls the function with the argument std::get<I>(t)and increases the index by one. The class std::integral_constantis needed in order to resolve the value of Iat compile time. The std::enable_ifSFINAE stuff is used to help the compiler separate this overload from the previous one, and call this overload only if the Iis smaller than the tuple size (on Coliru this is needed, whereas in Visual Studio it works without).

  • The third starts the recursion with I=0. It is the overload which is usually called from outside.

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

  • 第二个重载调用带有参数的函数std::get<I>(t)并将索引增加一。std::integral_constant为了I在编译时解析 的值,需要该类。该std::enable_ifSFINAE东西是用来帮助编译器从以前的一个分离这种过载,并调用此重载仅在I比元组大小(上Coliru这是必要的,而在Visual Studio它工作没有)。

  • 第三个以 开始递归I=0。它是通常从外部调用的过载。







EDIT:I've also included the idea mentioned by Yakk to additionally support std::arrayand std::pairby using a general template parameter TupleTypeinstead of one that is specialized for std::tuple<Ts ...>.

编辑:我还包括 Yakk 提到的想法,以额外支持std::arraystd::pair使用通用模板参数TupleType而不是专门用于std::tuple<Ts ...>.

As TupleTypetype needs to be deduced and is such a "universal reference", this further has the advantage that one gets perfect forwarding for free. The downside is that one has to use another indirection via typename std::remove_reference<TupleType>::type, as TupleTypemight also be a deduced as a reference type.

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