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

2022-01-19 00:00:00 tuples c++ c++11

我做了以下元组:

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

auto some = make_tuple("我很好", 255, 2.1);

解决方案

这里尝试将元组的迭代分解为组件部分.

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

template无效do_in_order(Fs&&... fs){int未使用[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... }(无效)未使用;//阻止警告}

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

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

模板constexpr std::make_index_sequence<std::tuple_size<T>::value>get_indexes(T const&){ 返回 {};}

肉和土豆:

template

以及面向公众的界面:

templatevoid for_each(元组&& tup, F&& f) {自动索引 = get_indexes(tup);for_each(索引,std::forward<元组>(tup),std::forward(f));}

当它声明 Tuple 时,它适用于 std::arrays 和 std::pairs.它还将所述对象的 r/l 值类别向下转发到它调用的函数对象.另请注意,如果您在自定义类型上有一个免费函数 get<N>,并且您覆盖了 get_indexes,则上述 for_each 将适用您的自定义类型.

如前所述,许多编译器不支持 do_in_order,因为它们不喜欢将未扩展参数包扩展为参数包的 lambda.

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

template(std::forward(tup)), 0 )... }(无效)未使用;//阻止警告}

这并不需要太多冗长,但我个人觉得不太清楚.在我看来,do_in_order 工作原理的影子魔法被内联的方式掩盖了.

index_sequence(和支持模板)是可以用 C++11 编写的 C++14 功能.在堆栈溢出上找到这样的实现很容易.当前最热门的谷歌点击是一个不错的 O(lg(n)) 深度实现,如果我正确阅读评论可能会是实际 gcc make_integer_sequence 至少一次迭代的基础(注释还指出了围绕消除 sizeof... 调用的一些进一步的编译时改进).p>

我们也可以这样写:

模板void for_each_arg(F&&f,Args&&...args){使用丢弃=int[];(void)丢弃{0,((void)(f(std::forward<Args>(args))),0)...};}

然后:

template

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

在 C++1z 中,我们还可以使用带有 for_each_arg 函数对象的 std::apply 来消除索引摆弄.

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 .

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

解决方案

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:

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.

As a side benefit, my code supports std::pair and std::array iteration:

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 Tuple it 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_each will work on your custom type.

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

We can inline do_in_order in that case

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_order works is obscured by doing it inline in my opinion.

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

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 Is via the auto&&i parameter.

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

相关文章