How to iterate over a std::tuple in C++ 11 [duplicate]
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);
Solution 1:
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.
Solution 2:
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::array
s and std::pair
s. 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.
Solution 3:
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 ifI
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 classstd::integral_constant
is needed in order to resolve the value ofI
at compile time. Thestd::enable_if
SFINAE stuff is used to help the compiler separate this overload from the previous one, and call this overload only if theI
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.