【问题标题】:How can you iterate over the elements of an std::tuple?如何迭代 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

标签: c++ c++11 iteration template-meta-programming stdtuple


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

在 C++17 中,您可以使用 std::applyfold expression

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

打印元组的完整示例:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple t{42, '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} 内完成。我错了吗?这个答案强调这种排序:stackoverflow.com/a/16387374/2757035
【解决方案5】:

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

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

{
    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> items{1, '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的编译器编译,并且与标准库的设计保持一致:

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

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

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

            4. 对于 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;

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

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

              【讨论】:

              【解决方案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】:

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

                #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】:

                  在我在这里看到的所有答案中,herehere,我最喜欢 @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。

                        【讨论】:

                          猜你喜欢
                          • 2020-12-08
                          • 2018-09-26
                          • 1970-01-01
                          • 2015-01-10
                          • 1970-01-01
                          • 2011-11-19
                          • 2013-03-27
                          相关资源
                          最近更新 更多