So I'm trying to figure out how this works: C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args?
The piece of black magic I do not understand is this code fragment:
f(std::get<N>(std::forward<Tuple>(t))...)
it's the expression inside f
that I don't understand.
I understand that the expression somehow unpacks/expands what's inside t
into a list of arguments. But could someone care to explain how this is done? When I look at the definition of std::get
(http://en.cppreference.com/w/cpp/utility/tuple/get), I don't see how N
fits in...? As far as I can tell, N is a sequence of integers.
Based on what I can observe, I'm assuming that expressions in the form E<X>...
where X
is the sequence of types X1
. X2
, ... Xn
, the expression will be expanded as E<X1>, E<X2> ... E<Xn>
. Is this how it works?
Edit: In this case N is not a sequence of types, but integers. But I'm guessing this language construct applies to both types and values.
I think that @Xeo's comment summed it up well. From 14.5.3 of the C++11 standard:
A pack expansion consists of a pattern and an ellipsis, the instantiation of which produces zero or more instantiations of the pattern in a list.
In your case, by the time you finish with the recursive template instantiation and end up in the partial specialization, you have
f(std::get<N>(std::forward<Tuple>(t))...);
...where N
is parameter pack of four int
s (0
, 1
, 2
, and 3
). From the standardese above, the pattern here is
std::get<N>(std::forward<Tuple>(t))
The application of the ...
ellipsis to the above pattern causes it to be expanded into four instantiations in list form, i.e.
f(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t));
The fundamental ingredient to expanding the std::tuple<T...>
is actually omitted from the code: you need to obtain a a second parameter back: in addition to the list of types of the std::tuple<...>
you need a parameter pack with indices 0, 1, ..., n
. Once you have these two parameters packs, you can expand them in tandem:
template <typename F, typename... T, int... N>
void call_impl(F&& fun, std::tuple<T...>&& t) {
fun(std::get<N>(t)...);
}
The real magic lies in conjuring up the second parameter pack when you just have a std::tuple<T...>
. It takes a bit of template programming. Here is an approach to create the list of indices:
template <int... Indices> struct indices;
template <> struct indices<-1> { typedef indices<> type; };
template <int... Indices>
struct indices<0, Indices...>
{
typedef indices<0, Indices...> type;
};
template <int Index, int... Indices>
struct indices<Index, Indices...>
{
typedef typename indices<Index - 1, Index, Indices...>::type type;
};
template <typename T>
typename indices<std::tuple_size<T>::value - 1>::type const*
make_indices()
{
return 0;
}
So, if you have a function template, let's call it call()
which takes a function object and a std::tuple<T...>
with the arguments to the function. An easy approach is to rewrite the call_impl()
mentioned above to deal with deducing the indices:
template <typename F, typename Tuple, int... N>
void call_impl(F&& fun, Tuple&& t, indices<Indices...> const*)
{
fun(std::get<N>(t)...);
}
template <typename F, typename Tuple>
void call(F&& fun, Tuple&& t)
{
call_imle(std::forward<F>(fun), std::forward<Tuple>(t), make_indices<Tuple>());
}
What this code doesn't really extend is the correct use of std::forward<...>()
with the various std::tuple<...>
elements when calling the function. Just using std::forward<Tuple>(t)
does not work because it possibly moves the entire std::tuple<...>
rather than moving the elements. I think something like a suitable element-wise move of a std::tuple<...>
can be done but I haven't done it, yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With