How can you iterate over the elements of an std::tuple?

105,903

Solution 1

Boost.Fusion is a possibility:

Untested example:

struct DoSomething
{
    template<typename T>
    void operator()(T& t) const
    {
        t.do_sth();
    }
};

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

Solution 2

I have an answer based on 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);
}

The usual idea is to use compile time recursion. In fact, this idea is used to make a printf that is type safe as noted in the original tuple papers.

This can be easily generalized into a for_each for tuples:

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

Though this then requires some effort to have FuncT represent something with the appropriate overloads for every type the tuple might contain. This works best if you know all the tuple elements will share a common base class or something similar.

Solution 3

In C++17, you can use std::apply with fold expression:

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

A complete example for printing a 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]

This solution solves the issue of evaluation order in M. Alaggan's answer.

Solution 4

C++ is introducing expansion statements for this purpose. They were originally on track for C++20 but narrowly missed the cut due to a lack of time for language wording review (see here and here).

The currently agreed syntax (see the links above) is:

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

Solution 5

In C++17 you can do this:

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

This already works in Clang++ 3.9, using std::experimental::apply.

Share:
105,903
einpoklum
Author by

einpoklum

Made my way from the Olympus of Complexity Theory, Probabilistic Combinatorics and Property Testing to the down-to-earth domain of Heterogeneous and GPU Computing, and now I'm hoping to bring the gospel of GPU and massive-regularized parallelism to DBMS architectures. I've post-doc'ed at the DB architecture group in CWI Amsterdam to do (some of) that. I subscribe to most of Michael Richter's critique of StackOverflow; you might want to take the time to read it. If you listen closely you can hear me muttering "Why am I not socratic again already?"

Updated on April 23, 2022

Comments

  • einpoklum
    einpoklum about 2 years

    How can I iterate over a tuple (using C++11)? I tried the following:

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

    but this doesn't work:

    Error 1: sorry, unimplemented: cannot expand ‘Listener ...’ into a fixed-length argument list.
    Error 2: i cannot appear in a constant expression.

    So, how do I correctly iterate over the elements of a tuple?

    • Burkhard
      Burkhard almost 15 years
      May I ask, how you compile in C++0x? It is not released nor ready as far as I know.
    • AProgrammer
      AProgrammer almost 15 years
      g++ contains experimental support of some C++0X features, including variadic templates, since version 4.3. Other compilers do the same (with different feature sets, if you want to use them in production, you are back in the 90 with a wide variation of support for bleeding edge things)
    • Admin
      Admin almost 15 years
      I am using g++ version 4.4 with std=c++0x
    • Omnifarious
      Omnifarious over 11 years
      This question needs a C++11 update.
    • oblitum
      oblitum about 10 years
      @Omnifarious now, it needs a C++14 update
    • yves
      yves over 6 years
      A C++14 solution can be found on stack echange.
  • user2023370
    user2023370 over 12 years
    I tried your first solution, but it fails with this function on pairs. Any idea why?template <typename T, typename U> void addt(pair<T,U> p) { cout << p.first + p.second << endl; } int main(int argc, char *argv[]) { cout << "Hello." << endl; for_each(make_tuple(2,3,4), [](int i) { cout << i << endl; }); for_each(make_tuple(make_pair(1,2),make_pair(3,4)), addt); return 0; }
  • Faheem Mitha
    Faheem Mitha over 12 years
    Thanks for the nice simple example. For C++ beginners looking for background on how this works, see SFINAE and enable_if documentation.
  • Omnifarious
    Omnifarious over 11 years
    This could easily be generalized to be a generic for_each. In fact, I did it myself. :-) I think this answer would be more useful if it was already generalized.
  • Omnifarious
    Omnifarious over 11 years
    There, I added the generalization because I actually needed one, and I think it'd be useful for others to see.
  • sehe
    sehe about 11 years
    @ViktorSehr AFAICT it doesn't (at least on GCC 4.7.2)? Anyone with a hint?
  • sehe
    sehe about 11 years
    @ViktorSehr Found the problem: a bug/omission causes the behaviour of fusion to depend on the order of the includes, see Boost ticket #8418 for more details
  • lethal-guitar
    lethal-guitar about 11 years
    Note: You might also need versions with const std::tuple<Tp...>&.. If you don't intend to modify tuples while iterating, those const versions will suffice.
  • Algebraic Pavel
    Algebraic Pavel almost 10 years
    Thanks a lot for the nice solution :-). Actually, in the "for each" function, you can overcome the difficulty using a functor, e.g., struct printer { template<typename T> void operator()(const T& t) const { ... } }; as passing a "templated printer" won't work.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 9 years
    Should cast the return value of foo to void before invoking operator, to avoid possible pathological operator overloading.
  • Gerard
    Gerard over 9 years
    How would you break from this loop? Or return from a function that uses this loop (it seems return is now scoped to the loop, but does not seem to work as a way to break from it)
  • emsr
    emsr over 9 years
    @Gerard The first template inline typename std::enable_if<I == sizeof...(Tp), void>::type breaks from he loop on encountering the last tuple element.
  • Gerard
    Gerard over 9 years
    Yes, but I meant is it possible to break manually before reaching the last element?
  • emsr
    emsr over 9 years
    Not as written.. You could make a version with the indexing flipped - start at I=sizeof...(Tp) and count down. Then supply a maximum number of args explicitly. You could also make a version that broke on a tag type, say break_t. Then you would put an object of that tag type in your tuple when you wanted to stop printing. Or you could supply a stop type as a template parm. Obviously you couldn't break at run time.
  • Marcin
    Marcin over 9 years
    need to use boost::fusion::tuple instead of std::tuple to have this working.
  • Bulletmagnet
    Bulletmagnet over 7 years
    Please please please don't go using namespace boost::fusion (especially together with using namespace std). Now there's no way to know whether that for_each is std::for_each or boost::fusion::for_each
  • oblitum
    oblitum over 7 years
    @Bulletmagnet this was done for terseness here and ADL can handle that without a problem. Besides, it's also function local.
  • underscore_d
    underscore_d over 7 years
    Doesn't this lead to the iteration - i.e. calls of do_something() - occurring in an unspecified order, because the parameter pack is expanded within a function call (), wherein arguments have unspecified ordering? That might be very significant; I'd imagine most people would expect the ordering to be guaranteed to occur in the same order as the members, i.e. as the indices to std::get<>(). AFAIK, to get guaranteed ordering in cases like this, the expansion must be done within {braces}. Am I wrong? This answer puts emphasis on such ordering: stackoverflow.com/a/16387374/2757035
  • joki
    joki about 6 years
    It's a shame this answer is written so verbose because I think the way of iterating (for_each_impl) is the most elegant of all the solutions I've seen.
  • Hossein
    Hossein almost 6 years
    This for_each looks like a good solution in most cases. In my case though, I needed to pass a member function as the f. A wrapper like std::mem_fun will not help because it requires specification of the class type I'm trying to call the member function of. Of course, that would not be possible because each item in the tuple has a different type. I had to manually hack through the tuple and call the member function.:( Here is a related post by someone else: stackoverflow.com/questions/43259803/…
  • Hossein
    Hossein almost 6 years
    I realised whilst std::mem_fun is not the key, generic lambdas of C++17 are: auto t = my_tuple(); for_each(t, [](auto& e){e.mf();}); where mf is the name of the member function I'd like to call. In my case, my mf has important side-effects and I pass by non-const reference. YMMV.
  • helmesjo
    helmesjo over 5 years
    Could you explain what is happening here: ((std::cout << args << '\n'), ...); ? The lambda is invoked once with the tuple-elements unpacked as args, but what's up with the double parentheses?
  • xskxzr
    xskxzr over 5 years
    @helmesjo It expands to a comma expression ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')) here.
  • Hossein
    Hossein about 5 years
    Under GCC 8.1/mingw-64, I get two warnings for the use of boost::fusion::for_each with std lambda expressions: boost/mpl/assert.hpp:188:21: warning: unnecessary parentheses in declaration of 'assert_arg' [-Wparentheses] failed ************ (Pred::************ boost/mpl/assert.hpp:193:21: warning: unnecessary parentheses in declaration of 'assert_not_arg' [-Wparentheses] failed ************ (boost::mpl::not_<Pred>::************
  • Admin
    Admin almost 5 years
    Thanks for example but it sucks to go through so much trouble and research to use C++ features. It seems more like punishment for using c++ features.
  • emsr
    emsr almost 5 years
    @JaveneCPPMcGowan Yes, template metaprogramming (TMP) is difficult. There is a strong push to make the standard library work in constexpr contexts for sane non-TMP programming. C++20 has most of algorithm and utility done. Even <string> and <vector> are partially constexpr.
  • Miral
    Miral over 4 years
    Note that in case you want to do things that aren't legal in a comma-expression (such as declaring variables and blocks), you can put all of that into a method and simply call it from within the folded-comma-expression.
  • Leonard
    Leonard about 4 years
    N.B: It appears that std::enable_if< I < sizeof...(Ts), void>::type still bamboozles both the XCode and Doxygen parser, which disables automatic indentation and documentation generation below that line. Use () around the comparison.
  • Lewis Kelsey
    Lewis Kelsey about 4 years
    Also you don't need inline or void here, it's already inline and void is default
  • Toby Speight
    Toby Speight over 3 years
    If we have if constexpr, we also have std::apply(), which is easier than using a recursive template.