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

Mos*_*ebi 19 c++ tuples c++11

我做了以下元组:

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

auto some = make_tuple("I am good", 255, 2.1);
Run Code Online (Sandbox Code Playgroud)

T.C*_*.C. 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)>());
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

演示.

std::index_sequence和C系列是C++ 14的特性,但它们可以很容易地用C++ 11实现(SO上有很多可用的).多态lambda也是C++ 14,但可以用自定义编写的仿函数替换.

  • 这些是可变参数模板:http://msdn.microsoft.com/en-us/library/dn439779.aspx和http://en.cppreference.com/w/cpp/language/parameter_pack (5认同)
  • +1,但你应该提到这只是c ++ 14 ..(标题是c ++ 11) (2认同)

Yak*_*ont 25

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

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

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

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

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

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

肉和土豆:

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)) ); }... );
}
Run Code Online (Sandbox Code Playgroud)

和面向公众的界面:

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) );
}
Run Code Online (Sandbox Code Playgroud)

虽然它声明Tuple它适用于std::arrays和std::pairs.它还将所述对象的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
}
Run Code Online (Sandbox Code Playgroud)

这不会花费太多冗长,但我个人觉得不太清楚.do_in_order在我看来,通过内联实现,工作方式的影子魔法是模糊不清的.

index_sequence(和支持模板)是一个C++ 14功能,可以用C++ 11编写.在堆栈溢出上找到这样的实现很容易.目前最热门的谷歌热门是一个体面的O(lg(n))深度实现,如果我正确阅读评论可能是实际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)...};
}
Run Code Online (Sandbox Code Playgroud)

然后:

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))...
  );
}
Run Code Online (Sandbox Code Playgroud)

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

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


dav*_*igh 11

这是一个类似的,比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>());
}
Run Code Online (Sandbox Code Playgroud)

用法(带std::tuple):

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

用法(带std::array):

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

DEMO


总体思路:如在TC的解决方案中,从索引开始I=0并达到元组的大小.然而,这里不是按照可变的扩展而是一次一个地进行.

说明:

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

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

  • 第三个开始递归I=0.通常从外部调用过载.




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

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