如何迭代 std::tuple 的元素?

Posted

技术标签:

【中文标题】如何迭代 std::tuple 的元素?【英文标题】:How can you iterate over the elements of an std::tuple? 【发布时间】:2010-11-14 22:58:54 【问题描述】:

如何迭代元组(使用 C++11)?我尝试了以下方法:

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
  std::get<i>(my_tuple).do_sth();

但这不起作用:

错误 1:抱歉,未实现:无法将“Listener ...”扩展为固定长度的参数列表。 错误 2:i 不能出现在常量表达式中。

那么,如何正确迭代元组的元素?

【问题讨论】:

请问,你是怎么用C++0x编译的?据我所知,它还没有发布也没有准备好。 g++ 从 4.3 版开始包含对一些 C++0X 特性的实验性支持,包括可变参数模板。其他编译器也这样做(具有不同的功能集,如果你想在生产中使用它们,你回到了 90 年代,对最前沿的东西提供了广泛的支持) 我正在使用 g++ 4.4 版和 std=c++0x 这个问题需要 C++11 更新。 @Omnifarious 现在需要C++14 update 【参考方案1】:

我有一个基于Iterating over a Tuple的答案:

#include <tuple>
#include <utility> 
#include <iostream>

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
   

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  print(std::tuple<Tp...>& t)
  
    std::cout << std::get<I>(t) << std::endl;
    print<I + 1, Tp...>(t);
  

int
main()

  typedef std::tuple<int, float, double> T;
  T t = std::make_tuple(2, 3.14159F, 2345.678);

  print(t);

通常的想法是使用编译时递归。事实上,这个想法被用来制作一个类型安全的 printf,如原始元组论文中所述。

这可以很容易地概括为元组的for_each

#include <tuple>
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...> &, FuncT) // Unused arguments are given no names.
   

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_each(std::tuple<Tp...>& t, FuncT f)
  
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  

尽管这需要一些努力才能让FuncT 表示具有适合元组可能包含的每种类型的重载的东西。如果您知道所有元组元素都将共享一个公共基类或类似的东西,那么这种方法效果最好。

【讨论】:

感谢您提供的简单示例。对于想了解其工作原理的 C++ 初学者,请参阅 SFINAE 和 enable_if documentation。 这可以很容易地概括为一个通用的for_each。事实上,我是自己做的。 :-) 我认为如果它已经被概括,这个答案会更有用。 这里,我添加了概括,因为我确实需要一个,我认为它对其他人来说会很有用。 注意:您可能还需要带有const std::tuple&lt;Tp...&gt;&amp;..的版本。如果您不打算在迭代时修改元组,那些const 版本就足够了。 不像写的那样。你可以制作一个索引翻转的版本 - 从 I=sizeof...(Tp) 开始并倒计时。然后显式提供最大数量的参数。你也可以制作一个破坏标签类型的版本,比如break_t。然后,当您想停止打印时,您可以将该标记类型的对象放入您的元组中。或者您可以提供一个停止类型作为模板参数。显然你不能在运行时中断。【参考方案2】:

在 C++17 中,您可以使用 std::apply 和 fold expression:

std::apply([](auto&&... args) ((/* args.dosomething() */), ...);, the_tuple);

打印元组的完整示例:

#include <tuple>
#include <iostream>

int main()

    std::tuple t42, 'a', 4.2; // Another C++17 feature: class template argument deduction
    std::apply([](auto&&... args) ((std::cout << args << '\n'), ...);, t);

[Online Example on Coliru]

此方案解决了M. Alaggan's answer中的求值顺序问题。

【讨论】:

您能解释一下这里发生了什么:((std::cout &lt;&lt; args &lt;&lt; '\n'), ...); 吗? lambda 被调用一次,元组元素解包为args,但是双括号是怎么回事? @helmesjo 这里扩展为逗号表达式((std::cout &lt;&lt; arg1 &lt;&lt; '\n'), (std::cout &lt;&lt; arg2 &lt;&lt; '\n'), (std::cout &lt;&lt; arg3 &lt;&lt; '\n')) 请注意,如果您想在逗号表达式中做一些不合法的事情(例如声明变量和块),您可以将所有这些放入一个方法中并简单地从在折叠逗号表达式中。【参考方案3】:

Boost.Fusion 是一种可能性:

未经测试的示例:

struct DoSomething

    template<typename T>
    void operator()(T& t) const
    
        t.do_sth();
    
;

tuple<....> t = ...;
boost::fusion::for_each(t, DoSomething());

【讨论】:

@ViktorSehr AFAICT 它没有(至少在 GCC 4.7.2 上)?有人有提示吗? @ViktorSehr 发现问题:一个错误/遗漏导致融合的行为取决于包含的顺序,请参阅 Boost ticket #8418 了解更多详情 需要使用 boost::fusion::tuple 而不是 std::tuple 才能正常工作。 在 GCC 8.1/mingw-64 下,我收到两个关于使用带有 std lambda 表达式的 boost::fusion::for_each 的警告: boost/mpl/assert.hpp:188:21: 警告: 'assert_arg' [-Wparentheses] 声明中不必要的括号失败 ************ (Pred::************ boost/mpl/assert.hpp:193 :21: 警告:'assert_not_arg' [-Wparentheses] 声明中不必要的括号失败 ************ (boost::mpl::not_::******* *****【参考方案4】:

在 C++17 中,您可以这样做:

std::apply([](auto ...x)std::make_tuple(x.do_something()...); , the_tuple);

这已经在 Clang++ 3.9 中工作,使用 std::experimental::apply。

【讨论】:

这不会导致迭代 - 即 do_something() 的调用 - 以未指定的顺序发生,因为参数包在函数调用 () 中展开,其中参数具有未指定的顺序?这可能非常重要;我想大多数人都希望保证以与成员相同的顺序发生排序,即作为std::get&lt;&gt;() 的索引。 AFAIK,为了在这种情况下获得保证订购,扩展必须在braces 内完成。我错了吗?这个答案强调这种排序:***.com/a/16387374/2757035【参考方案5】:

C++ 为此目的引入了expansion statements。他们最初在 C++20 的轨道上,但由于缺乏语言措辞审查的时间而差点错过(参见 here 和 here)。

目前商定的语法(见上面的链接)是:


    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;

【讨论】:

【参考方案6】:

使用 Boost.Hana 和通用 lambda:

#include <tuple>
#include <iostream>
#include <boost/hana.hpp>
#include <boost/hana/ext/std/tuple.hpp>

struct Foo1 
    int foo() const  return 42; 
;

struct Foo2 
    int bar = 0;
    int foo()  bar = 24; return bar; 
;

int main() 
    using namespace std;
    using boost::hana::for_each;

    Foo1 foo1;
    Foo2 foo2;

    for_each(tie(foo1, foo2), [](auto &foo) 
        cout << foo.foo() << endl;
    );

    cout << "foo2.bar after mutation: " << foo2.bar << endl;

http://coliru.stacked-crooked.com/a/27b3691f55caf271

【讨论】:

请不要去using namespace boost::fusion(尤其是和using namespace std一起)。现在无法知道for_eachstd::for_each 还是boost::fusion::for_each @Bulletmagnet 这样做是为了简洁,ADL 可以毫无问题地处理它。此外,它也是本地函数。【参考方案7】:

在 C++17 中使用 if constexpr 实现此操作的更简单、直观且对编译器友好的方式:

// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) 
    std::cout << std::get<I>(t) << " ";
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>(t);

这是编译时递归,类似于@emsr 提出的递归。但这不使用 SFINAE,所以(我认为)它对编译器更友好。

【讨论】:

【参考方案8】:

这是一种简单的 C++17 方法,只需标准库即可迭代元组项:

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
        std::tuple_size_v<
            std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
>
void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)

    if constexpr (Index < Size)
    
        std::invoke(callable, args..., std::get<Index>(tuple));

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    

例子:

#include <iostream>

int main()

    std::tuple<int, char> items1, 'a';
    for_each(items, [](const auto& item) 
        std::cout << item << "\n";
    );

输出:

1
a

这可以扩展为在可调用对象返回值的情况下有条件地中断循环(但仍适用于不返回布尔可分配值的可调用对象,例如 void):

#include <tuple>      // std::tuple
#include <functional> // std::invoke

template <
    size_t Index = 0, // start iteration at 0 index
    typename TTuple,  // the tuple type
    size_t Size =
    std::tuple_size_v<
    std::remove_reference_t<TTuple>>, // tuple size
    typename TCallable, // the callable to bo invoked for each tuple item
    typename... TArgs   // other arguments to be passed to the callable 
    >
    void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args)

    if constexpr (Index < Size)
    
        if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
        
            if (!std::invoke(callable, args..., std::get<Index>(tuple)))
                return;
        
        else
        
            std::invoke(callable, args..., std::get<Index>(tuple));
        

        if constexpr (Index + 1 < Size)
            for_each<Index + 1>(
                std::forward<TTuple>(tuple),
                std::forward<TCallable>(callable),
                std::forward<TArgs>(args)...);
    

例子:

#include <iostream>

int main()

    std::tuple<int, char> items 1, 'a' ;
    for_each(items, [](const auto& item) 
        std::cout << item << "\n";
    );

    std::cout << "---\n";

    for_each(items, [](const auto& item) 
        std::cout << item << "\n";
        return false;
    );

输出:

1
a
---
1

【讨论】:

【参考方案9】:

您需要使用模板元编程,这里显示为 Boost.Tuple:

#include <boost/tuple/tuple.hpp>
#include <iostream>

template <typename T_Tuple, size_t size>
struct print_tuple_helper 
    static std::ostream & print( std::ostream & s, const T_Tuple & t ) 
        return print_tuple_helper<T_Tuple,size-1>::print( s, t ) << boost::get<size-1>( t );
    
;

template <typename T_Tuple>
struct print_tuple_helper<T_Tuple,0> 
    static std::ostream & print( std::ostream & s, const T_Tuple & ) 
        return s;
    
;

template <typename T_Tuple>
std::ostream & print_tuple( std::ostream & s, const T_Tuple & t ) 
    return print_tuple_helper<T_Tuple,boost::tuples::length<T_Tuple>::value>::print( s, t );


int main() 

    const boost::tuple<int,char,float,char,double> t( 0, ' ', 2.5f, '\n', 3.1416 );
    print_tuple( std::cout, t );

    return 0;

在 C++0x 中,您可以将 print_tuple() 编写为可变参数模板函数。

【讨论】:

【参考方案10】:

首先定义一些索引助手:

template <size_t ...I>
struct index_sequence ;

template <size_t N, size_t ...I>
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> ;

template <size_t ...I>
struct make_index_sequence<0, I...> : public index_sequence<I...> ;

您希望在每个元组元素上应用您的函数:

template <typename T>
/* ... */ foo(T t)  /* ... */ 

你可以写:

template<typename ...T, size_t ...I>
/* ... */ do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) 
    std::tie(foo(std::get<I>(ts)) ...);


template <typename ...T>
/* ... */ do_foo(std::tuple<T...> &ts) 
    return do_foo_helper(ts, make_index_sequence<sizeof...(T)>());

或者如果foo返回void,使用

std::tie((foo(std::get<I>(ts)), 1) ... );

注意:在 C++14 上,make_index_sequence 已定义 (http://en.cppreference.com/w/cpp/utility/integer_sequence)。

如果您确实需要从左到右的评估顺序,请考虑以下内容:

template <typename T, typename ...R>
void do_foo_iter(T t, R ...r) 
    foo(t);
    do_foo(r...);


void do_foo_iter() 

template<typename ...T, size_t ...I>
void do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) 
    do_foo_iter(std::get<I>(ts) ...);


template <typename ...T>
void do_foo(std::tuple<T...> &ts) 
    do_foo_helper(ts, make_index_sequence<sizeof...(T)>());

【讨论】:

应该在调用operator,之前将foo的返回值转换为void以避免可能的病态运算符重载。【参考方案11】:

如果你想使用 std::tuple 并且你有支持可变参数模板的 C++ 编译器,试试下面的代码(用 g++4.5 测试)。这应该是您问题的答案。

#include <tuple>

// ------------- UTILITY---------------
template<int...> struct index_tuple; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
 
    typedef index_tuple<Indexes...> type; 
; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
; 

// ----------- FOR EACH -----------------
template<typename Func, typename Last>
void for_each_impl(Func&& f, Last&& last)

    f(last);


template<typename Func, typename First, typename ... Rest>
void for_each_impl(Func&& f, First&& first, Rest&&...rest) 

    f(first);
    for_each_impl( std::forward<Func>(f), rest...);


template<typename Func, int ... Indexes, typename ... Args>
void for_each_helper( Func&& f, index_tuple<Indexes...>, std::tuple<Args...>&& tup)

    for_each_impl( std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...);


template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>& tup, Func&& f)

   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );


template<typename Func, typename ... Args>
void for_each( std::tuple<Args...>&& tup, Func&& f)

   for_each_helper(std::forward<Func>(f), 
                   typename make_indexes<Args...>::type(), 
                   std::forward<std::tuple<Args...>>(tup) );

boost::fusion 是另一种选择,但它需要自己的元组类型:boost::fusion::tuple。让我们更好地坚持标准!这是一个测试:

#include <iostream>

// ---------- FUNCTOR ----------
struct Functor 

    template<typename T>
    void operator()(T& t) const  std::cout << t << std::endl; 
;

int main()

    for_each( std::make_tuple(2, 0.6, 'c'), Functor() );
    return 0;

可变参数模板的力量!

【讨论】:

我尝试了您的第一个解决方案,但在配对时使用此功能失败。知道为什么吗?template void addt(pair p) cout 很遗憾这个答案写得如此冗长,因为我认为迭代 (for_each_impl) 的方式是我见过的所有解决方案中最优雅的。【参考方案12】:

在 MSVC STL 中有一个 _For_each_tuple_element 函数(未记录):

#include <tuple>

// ...

std::tuple<int, char, float> values;
std::_For_each_tuple_element(values, [](auto&& value)

    // process 'value'
);

【讨论】:

【参考方案13】:

其他人提到了一些设计精良的第三方库,您可能会求助于这些库。但是,如果您在没有这些第三方库的情况下使用 C++,以下代码可能会有所帮助。

namespace detail 

template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper 
  template <class UnaryFunction>
  static void apply(Tuple&& tp, UnaryFunction& f) 
    f(std::get<I>(std::forward<Tuple>(tp)));
    for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
  
;

template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
    I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type> 
  template <class UnaryFunction>
  static void apply(Tuple&&, UnaryFunction&) 
;

  // namespace detail

template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f) 
  detail::for_each_in_tuple_helper<Tuple, 0u>
      ::apply(std::forward<Tuple>(tp), f);
  return std::move(f);

注意:代码可以用任何支持C++11的编译器编译,并且与标准库的设计保持一致:

    元组不必是std::tuple,而是可以是任何支持std::getstd::tuple_size的东西;特别是可以使用std::arraystd::pair

    元组可以是引用类型或 cv 限定的;

    std::for_each行为类似,返回输入UnaryFunction

    对于 C++14(或最新版本)用户,typename std::enable_if&lt;T&gt;::typetypename std::decay&lt;T&gt;::type 可以替换为其简化版本 std::enable_if_t&lt;T&gt;std::decay_t&lt;T&gt;

    对于 C++17(或最新版本)用户,std::tuple_size&lt;T&gt;::value 可以替换为其简化版本 std::tuple_size_v&lt;T&gt;

    对于 C++20(或最新版本)用户,SFINAE 功能可以通过 Concepts 实现。

【讨论】:

【参考方案14】:

使用constexprif constexpr(C++17) 这相当简单直接:

template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup) 
  if constexpr (I == sizeof...(Ts)) 
    return;
   else 
    std::cout << std::get<I>(tup) << ' ';
    print<I+1>(tup);
  

【讨论】:

如果我们有if constexpr,we also have std::apply(),这比使用递归模板更容易。【参考方案15】:

另一种选择是为元组实现迭代器。这样做的好处是您可以使用标准库和基于范围的 for 循环提供的各种算法。此处https://foonathan.net/2017/03/tuple-iterator/ 解释了一种优雅的方法。基本思想是使用begin()end() 方法将元组变成一个范围以提供迭代器。迭代器本身返回一个std::variant&lt;...&gt;,然后可以使用std::visit 访问它。

这里有一些例子:

auto t = std::tuple 1, 2.f, 3.0 ;
auto r = to_range(t);

for(auto v : r)

    std::visit(unwrap([](auto& x)
        
            x = 1;
        ), v);


std::for_each(begin(r), end(r), [](auto v)
    
        std::visit(unwrap([](auto& x)
            
                x = 0;
            ), v);
    );

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    
        return acc + std::visit(unwrap([](auto& x)
        
            return static_cast<double>(x);
        ), v);
    );

std::for_each(begin(r), end(r), [](auto v)

    std::visit(unwrap([](const auto& x)
        
            std::cout << x << std::endl;
        ), v);
);

std::for_each(begin(r), end(r), [](auto v)

    std::visit(overload(
        [](int x)  std::cout << "int" << std::endl; ,
        [](float x)  std::cout << "float" << std::endl; ,
        [](double x)  std::cout << "double" << std::endl; ), v);
);

我的实现(很大程度上基于上面链接中的解释):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator

public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    

    

    tuple_iterator operator++()
    
        ++index_;
        return *this;
    

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    
        return index_ == other.index();
    

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    
        return index_ != other.index();
    

    auto operator*()  return std::invoke(acc_, index_); 

    [[nodiscard]] int index() const  return index_; 

private:
    const Accessor acc_;
    int index_;
;

template<bool IsConst, typename...Ts>
struct tuple_access

    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    
        return  [](tuple_ref t) -> variant_type
        
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
         ;
    

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    
        return  create_accessor<Is>()... ;
    

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>); 
;

template<bool IsConst, typename...Ts>
class tuple_range

public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    
    

    [[nodiscard]] auto begin() const 
     
        return tuple_iterator create_accessor(), 0 ;
    

    [[nodiscard]] auto end() const 
     
        return tuple_iterator create_accessor(), tuple_size ;
    

private:
    tuple_ref tuple_;

    auto create_accessor() const
     
        return [this](int idx)
        
            return std::invoke(tuple_access_type::table[idx], tuple_);
        ;
    
;

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)

    return r.begin();


template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)

    return r.end();


template <class ... Fs>
struct overload : Fs... 
    explicit overload(Fs&&... fs) : Fs fs ... 
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    
        return (*this)(ref.get());
    

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    
        return (*this)(ref.get());
    
;

template <class F>
struct unwrap : overload<F>

    explicit unwrap(F&& f) : overload<F> std::forward<F>(f)  
    using overload<F>::operator();
;

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)

    return tuple_range<false, Ts...>t;


template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)

    return tuple_range<true, Ts...>t;



#endif

通过将const std::tuple&lt;&gt;&amp; 传递给to_range() 也支持只读访问。

【讨论】:

【参考方案16】:

我可能错过了这趟火车,但这里将供将来参考。 这是我基于 answer 和 gist 的构造:

#include <tuple>
#include <utility>

template<std::size_t N>
struct tuple_functor

    template<typename T, typename F>
    static void run(std::size_t i, T&& t, F&& f)
    
        const std::size_t I = (N - 1);
        switch(i)
        
        case I:
            std::forward<F>(f)(std::get<I>(std::forward<T>(t)));
            break;

        default:
            tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f));
        
    
;

template<>
struct tuple_functor<0>

    template<typename T, typename F>
    static void run(std::size_t, T, F)
;

然后按如下方式使用它:

template<typename... T>
void logger(std::string format, T... args) //behaves like C#'s String.Format()

    auto tp = std::forward_as_tuple(args...);
    auto fc = [](const auto& t)std::cout << t;;

    /* ... */

    std::size_t some_index = ...
    tuple_functor<sizeof...(T)>::run(some_index, tp, fc);

    /* ... */

可能还有改进的余地。


根据 OP 的代码,它会变成这样:

const std::size_t num = sizeof...(T);
auto my_tuple = std::forward_as_tuple(t...);
auto do_sth = [](const auto& elem)/* ... */;
for(int i = 0; i < num; ++i)
    tuple_functor<num>::run(i, my_tuple, do_sth);

【讨论】:

【参考方案17】:

在我在这里看到的所有答案中,here 和 here,我最喜欢 @sigidagi 的迭代方式。不幸的是,他的回答非常冗长,我认为这掩盖了内在的清晰性。

这是我的他的解决方案版本,它更简洁,适用于std::tuplestd::pairstd::array

template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)


/**
 * Invoke the unary function with each of the arguments in turn.
 */
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)

    f(std::forward<Arg0>(a0));
    invoke_with_arg(std::move(f), std::forward<Args>(as)...);


template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)

    using std::get;
    invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);


/**
 * Invoke the unary function for each of the elements of the tuple.
 */
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)

    using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
    for_each_helper(
        std::forward<Tuple>(t),
        std::move(f),
        std::make_index_sequence<size::value>()
    );

演示:coliru

C++14的std::make_index_sequence可以实现for C++11。

【讨论】:

【参考方案18】:

扩展@Stypox 答案,我们可以使他们的解决方案更通用(C++17 以后)。通过添加可调用函数参数:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) 
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) 
        for_each_apply<I+1>(t, std::forward<F>(f));
    

那么,我们需要一个策略来访问每种类型。

让我们从一些助手开始(前两个取自 cppreference):

template<class... Ts> struct overloaded : Ts...  using Ts::operator()...; ;
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref  using type = std::variant<std::reference_wrapper<Ts>...>; ;

variant_ref 用于允许修改元组的状态。

用法:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) 
                   std::visit(overloaded 
                       [](Foo &arg)  arg.foo(); ,
                       [](Bar const &arg)  arg.bar(); ,
                   , v);
               );

结果:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

为了完整起见,这里是我的BarFoo

struct Foo 
    void foo() std::cout << "Foo" << i++ << std::endl;
    int i = 0;
;
struct Bar 
    void bar() const std::cout << "Bar" << std::endl;
;

【讨论】:

【参考方案19】:

我在迭代一组函数对象时偶然发现了同样的问题,所以这里还有一个解决方案:

#include <tuple> 
#include <iostream>

// Function objects
class A 

    public: 
        inline void operator()() const  std::cout << "A\n"; ;
;

class B 

    public: 
        inline void operator()() const  std::cout << "B\n"; ;
;

class C 

    public:
        inline void operator()() const  std::cout << "C\n"; ;
;

class D 

    public:
        inline void operator()() const  std::cout << "D\n"; ;
;


// Call iterator using recursion.
template<typename Fobjects, int N = 0> 
struct call_functors 

    static void apply(Fobjects const& funcs)
    
        std::get<N>(funcs)(); 

        // Choose either the stopper or descend further,  
        // depending if N + 1 < size of the tuple. 
        using caller = std::conditional_t
        <
            N + 1 < std::tuple_size_v<Fobjects>,
            call_functors<Fobjects, N + 1>, 
            call_functors<Fobjects, -1>
        >;

        caller::apply(funcs); 
    
;

// Stopper.
template<typename Fobjects> 
struct call_functors<Fobjects, -1>

    static void apply(Fobjects const& funcs)
    
    
;

// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)

    call_functors<Fobjects>::apply(funcs);
;


using namespace std; 

int main()

    using Tuple = tuple<A,B,C,D>; 

    Tuple functors = A, B, C, D; 

    call(functors); 

    return 0; 

输出:

A 
B 
C 
D

【讨论】:

【参考方案20】:

boost 的元组提供了辅助函数 get_head()get_tail(),所以你的辅助函数可能看起来像这样:

inline void call_do_sth(const null_type&) ;

template <class H, class T>
inline void call_do_sth(cons<H, T>& x)  x.get_head().do_sth(); call_do_sth(x.get_tail()); 

如此处所述http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

std::tuple 应该是相似的。

实际上,很遗憾std::tuple 似乎没有提供这样的接口,因此之前建议的方法应该可以工作,或者您需要切换到具有其他好处的boost::tuple(例如已经提供的io 运算符)。尽管使用 gcc 有 boost::tuple 的缺点 - 它还不接受可变参数模板,但这可能已经修复,因为我的机器上没有安装最新版本的 boost。

【讨论】:

以上是关于如何迭代 std::tuple 的元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 std::tuple 中合并 std::unordered_map?

检查所有std :: tuple元素是否满足条件+设计问题

std::tuple sizeof,是错过了优化吗?

将 std::tuple 的类型转换为 std::pair

插入 std::set<std::tuple<std::string, ...>> 时重复

在迭代期间同时修改映射