21

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);
Mostafa Talebi
  • 8,825
  • 16
  • 61
  • 105
  • 2
    Depends what you plan on doing after you "iterate" over it. – Rapptz Nov 13 '14 at 06:28
  • 1
    What would iterate on data of different types mean for you? – Basile Starynkevitch Nov 13 '14 at 06:31
  • I want to `cout` each element - the most basic iteration. Accessing each element for `cout`-ing... – Mostafa Talebi Nov 13 '14 at 06:31
  • 2
    Something like [this question](http://stackoverflow.com/questions/6245735/pretty-print-stdtuple) then? – Tony Delroy Nov 13 '14 at 06:33
  • @Tony D It seems then tuple utilization and implementation quite differs from traditional list data types such as array and/or vector..thanks... – Mostafa Talebi Nov 13 '14 at 06:34
  • 9
    @MostafaTalebi: true... `tuple`s can hold heterogeneous types, which can only be indexed at compile time as the compiler needs to vary the element-handling code appropriately for the type. Very different. `vector`, `array` etc. are homogenous containers, so runtime iteration and processing doesn't need any polymorphic dispatch. – Tony Delroy Nov 13 '14 at 06:51
  • Is the order of iteration important to you? – Martin York Nov 13 '14 at 15:10

3 Answers3

29
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)>());
}

Usage:

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

Demo.

std::index_sequence and family are C++14 features, but they can be easily implemented in C++11 (there are many available on SO). Polymorphic lambdas are also C++14, but can be replaced with a custom-written functor.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 3
    +1 but you should mention that this is c++14 only .. (the title is c++11) – quantdev Nov 13 '14 at 06:51
  • what concept is this: [class followed by "..." and then Tx], i dont know how should i pronounce it, the only thing i can understand is, it is giving variable argument for tuple's values as it is logically correct that tuple is gona have diff types, but even though i would love if some one could direct me to the right place from where i can learn this? – Rupesh Yadav. Nov 13 '14 at 07:06
  • 5
    These are variadic templates: http://msdn.microsoft.com/en-us/library/dn439779.aspx and http://en.cppreference.com/w/cpp/language/parameter_pack – Laura Maftei Nov 13 '14 at 07:15
26

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.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • ADL for `get` is a nice touch. `tuple_size` works on arrays and pairs too so I don't see the need for separate `get_indexes` overloads - also, `-> decltype( std::make_index_sequence< sizeof...(Ts) >() )` - why use `decltype`? – T.C. Nov 13 '14 at 21:15
  • @T.C. Well, I stared with `auto` then made it SFINAE by moving the `return` expression to the `->` and `return {}`. Didn't think to get rid of the `decltype`. Changed it to a direct `make_index_sequence` off `tuple_size` and killed overloads. And fixed a typo (missed a `...`). – Yakk - Adam Nevraumont Nov 13 '14 at 21:27
  • @T.C., Yakk: can one you explain why "ADL for `get`" is a "nice touch"? What is the advantage over directly using `std::get` in the function call? – davidhigh Nov 14 '14 at 09:43
  • 1
    @davidhigh So suppose you define your own `tuple`-like structure (I dunno, a fixed size matrix class?). If I do ADL-enabled `std::get` and you define your own free `get` in the namespace with your class, my ADL-enabled call to `get` may access it. You'd also have to specialize `std::tuple_size`, but such specializations are legal. Really, it is just a matter of trying to write overly generic code as a matter of habit. – Yakk - Adam Nevraumont Nov 14 '14 at 14:33
  • It appears to use the C++14 feature make_index_sequence. Which can be implemented in C++11 according to http://pdimov.com/cpp2/simple_cxx11_metaprogramming.html but makes the approach less useful for C++11 as the OP poster asked for. – kometen Jan 04 '16 at 18:37
  • @T.C. As an aside, with structured bindings, my ADL get may actualy be useful! Is only going to take 3 years! ;) – Yakk - Adam Nevraumont Aug 15 '16 at 15:25
  • it's a nice solution, but even if you manage to get it to compile in C++11, you can't use it in C++11 since you can't create lambdas or functions with auto parameters :( – dputros Nov 08 '19 at 08:25
  • @dput true, but auto parameter arguments was among the first C++14 features every compiler implemented. So there is that. – Yakk - Adam Nevraumont Nov 08 '19 at 12:25
12

Here is a similar and more verbose solution than the formerly accepted one given by T.C., which is maybe a little bit easier to understand (-- it's probably the same as thousand others out there in the net):

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

Usage (with std::tuple):

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

Usage (with std::array):

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

DEMO


General idea: as in the solution of T.C., start with an index I=0 and go up to the size of the tuple. However, here it is done not per variadic expansion but one-at-a-time.

Explanation:

  • The first overload of for_each is called if I is equal to the size of the tuple. The function then simply does nothing and such end the recursion.

  • The second overload calls the function with the argument std::get<I>(t) and increases the index by one. The class std::integral_constant is needed in order to resolve the value of I at compile time. The std::enable_if SFINAE stuff is used to help the compiler separate this overload from the previous one, and call this overload only if the I is smaller than the tuple size (on Coliru this is needed, whereas in Visual Studio it works without).

  • The third starts the recursion with I=0. It is the overload which is usually called from outside.




EDIT: I've also included the idea mentioned by Yakk to additionally support std::array and std::pair by using a general template parameter TupleType instead of one that is specialized for std::tuple<Ts ...>.

As TupleType type needs to be deduced and is such a "universal reference", this further has the advantage that one gets perfect forwarding for free. The downside is that one has to use another indirection via typename std::remove_reference<TupleType>::type, as TupleType might also be a deduced as a reference type.

davidhigh
  • 14,652
  • 2
  • 44
  • 75