Iterating over a std::tuple
I’ve been trying to wrap my brain around the new variadic templates capability in C++11 lately and wondered if it would be possible to write a generic routine to iterate over the members of a std::tuple
. I decided to start with the simple case of printing out the members of a tuple to the console via std::cout.
First, I came up with a compile time recursive definition of a variadic template struct that overloads the function call operator like so. You might wonder why I didn’t stick with a plain variadic template function instead. That will become evident in a moment.
template<int index, typename... Ts>
struct print_tuple {
void operator() (tuple<Ts...>& t) {
cout << get<index>(t) << " ";
print_tuple<index - 1, Ts...>{}(t);
}
};
Clearly, we’ll need to define a base case to break out of the compile time recursion for the print_tuple
call in the function call operator overload – which in this case would be when the non-type template parameter index
is equal to zero. So I went ahead and defined the following partial specialization of print_tuple
specializing on the value zero for the non-type template parameter index
.
template<typename... Ts>
struct print_tuple<0, Ts...> {
void operator() (tuple<Ts...>& t) {
cout << get<0>(t) << " ";
}
};
The reason I had to use a variadic template struct instead of a regular variadic template function is that it is not possible to partially specialize a template function in C++. Using a struct/class allows us to do so. Now that we have this setup, we can write a utility print
routine to wrap calls to print_tuple
and we should be done. Here’s a complete example:
#include <iostream>
#include <tuple>
using namespace std;
template<int index, typename... Ts>
struct print_tuple {
void operator() (tuple<Ts...>& t) {
cout << get<index>(t) << " ";
print_tuple<index - 1, Ts...>{}(t);
}
};
template<typename... Ts>
struct print_tuple<0, Ts...> {
void operator() (tuple<Ts...>& t) {
cout << get<0>(t) << " ";
}
};
template<typename... Ts>
void print(tuple<Ts...>& t) {
const auto size = tuple_size<tuple<Ts...>>::value;
print_tuple<size - 1, Ts...>{}(t);
}
int main() {
auto t = make_tuple(1, 2, "abc", "def", 4.0f);
print(t);
return 0;
}
All that print
does is to first determine the size of the tuple via tuple_size
and then instantiates print_tuple
and invokes the function call operator passing the tuple object in question. As you might have noticed we are essentially working our way backwards till we hit the base case where index
is zero – i.e. it’ll print the tuple members in the reverse order. Here’s the output this produces:
4 def abc 2 1
Iterate in order?
I figured, implementing a version that iterates over the tuple members in the order that they are specified should be fairly straightforward. We should just need to define a different base case (for the last item in the tuple instead of the first) and the recursive implementation should simply increment the index instead of decrementing it. Here’s what I came up with:
template<int index, typename... Ts>
struct print_tuple {
void operator() (tuple<Ts...>& t) {
cout << get<index>(t) << " ";
print_tuple<index + 1, Ts...>{}(t);
}
};
template<typename... Ts>
struct print_tuple<tuple_size<tuple<Ts...>>::value - 1, Ts...> {
void operator() (tuple<Ts...>& t) {
cout << get<tuple_size<tuple<Ts...>>::value - 1>(t) << " ";
}
};
The code shown in bold above are the changes of interest. In particular, turns out, the base case definition which handles the situation when print_tuple
is being instantiated with an index
that is equal to the size of the tuple minus one, is not really valid C++. Non-type template specialization in C++ can only be done using “simple identifiers”. The expression tuple_size<tuple<Ts...>>::value - 1
is a compile time constant for sure and ideally the compiler should be able to compute that value (which in fact it does for the code in the body of that method definition) but, well, it doesn’t! So, we’re kind of out of luck there.
Generalized iteration?
One might imagine that it should be possible to generalize this iteration (even if it can be done in reverse order only) so that we are able to supply arbitrary callbacks for processing tuple members. Turns out this again, is not possible which I think is reasonable. Because at that point one really needs to question whether using a tuple is the correct choice – a std::vector
or std::list
or some of the other containers maybe a more appropriate option. Having said that, you might be thinking that we should still be able to generalize this by adding another template parameter for a callback routine and passing in a template function for that parameter. Maybe something like this?
#include <iostream>
#include <tuple>
using namespace std;
template<int index, typename TCallback, typename... Ts>
struct iterate_tuple {
void operator() (tuple<Ts...>& t, TCallback callback) {
callback(get<index>(t));
iterate_tuple<index - 1, TCallback, Ts...>{}(t, callback);
}
};
template<typename TCallback, typename... Ts>
struct iterate_tuple<0, TCallback, Ts...> {
void operator() (tuple<Ts...>& t, TCallback callback) {
callback(get<0>(t));
}
};
template<typename TCallback, typename... Ts>
void for_each(tuple<Ts...>& t, TCallback callback) {
iterate_tuple<tuple_size<tuple<Ts...>>::value - 1, TCallback, Ts...> it;
it(t, callback);
}
template<typename T>
void print(T v) {
cout << v << " ";
}
int main() {
auto t = make_tuple(1, 2, "abc", "def", 4.0f);
for_each(t, print);
return 0;
}
This won’t work because the compiler needs to know the type of TCallback
when it is instantiating the for_each
template function which in this case it doesn’t and neither do we know the type because we want a different version of print to be used for each unique type in the tuple. If there is some way of telling the compiler to postpone resolution of TCallback
till it is actually used then this might have worked. As far as I know, that isn’t possible. But then I might be wrong. If you know a way of doing that, it’ll be great if you could please let me know in the comments.