Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpacking arguments from tuples

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.

like image 852
Jörgen Sigvardsson Avatar asked Sep 24 '13 20:09

Jörgen Sigvardsson


2 Answers

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 ints (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));
like image 79
Nate Kohl Avatar answered Oct 05 '22 23:10

Nate Kohl


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.

like image 32
Dietmar Kühl Avatar answered Oct 06 '22 01:10

Dietmar Kühl