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.