Tuple to parameter pack
Solution 1
Let's look at what happens here:
template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { };
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
The first one is a generic template, the second one is a specialization that applies when the first template parameter is 0.
Now, take a piece of paper and pencil, and write down how
gens<3>
gets defined by the above template. If your answer was:
struct gens<3> : public gens<2, 2>
then you were right. That's how the first template gets expanded when N
is "3", and ...S
is empty. gens<N - 1, N - 1, S...>
, therefore, becomes gens<2, 2>
.
Now, let's keep going, and see how gens<2, 2>
gets defined:
struct gens<2, 2> : public gens<1, 1, 2>
Here, in the template expansion, N
is 2, and ...S
is "2". Now, let's take the next step, and see how gens<1, 1, 2>
is defined:
struct gens<1, 1, 2> : public gens<0, 0, 1, 2>
Ok, now how does gens<0, 0, 1, 2>
gets defined? It can now be defined by the specialization:
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
So, what happens with struct gens<0, 0, 1, 2>
here? Well, in the specialization, "S..." becomes "0, 1, 2", so this becomes, in a manner of speaking:
struct gens<0, 0, 1, 2> {
typedef seq<0, 1, 2> type;
}
Now, keep in mind that all of these publicly inherit from each other, "elephant-style", so:
gens<3>::type
ends up being a typedef declaration for
struct seq<0, 1, 2>
And this is used, by the code that follows to convert the tuple into a parameter pack, using another template:
double delayed_dispatch()
{
return callFunc(typename gens<sizeof...(Args)>::type()); // Item #1
}
...Args
are the tuple parameters. So, if there are three elements in the tuple, sizeof(...Args)
is 3, and as I've explained above, gens<sizeof...(Args)>::type()
becomes gens<3>::type()
, a.k.a. seq<0, 1, 2>()
.
So, now:
template<int ...S>
double callFunc(seq<S...>)
{
return func(std::get<S>(params) ...);
}
The S...
part becomes "0, 1, 2", so the
std::get<S>(params)...
Becomes a parameter pack that gets expanded to:
std::get<0>(params), std::get<1>(params), std::get<2>(params),
And that's how a tuple becomes a parameter pack.
Solution 2
With C++17 you can use "if constexpr" to create a sequence wrapper:
template <int indxMax, template <int... > class spack, int ... seq>
constexpr auto get_seq17()
{
static_assert(indxMax >= 0, "Sequence size must be equal to or greater than 0!");
if constexpr (indxMax > 0)
{
typedef decltype(spack<indxMax, seq...>{}) frst;
constexpr int next = indxMax - 1;
return get_seq17<next, spack, indxMax, seq...>();
}
else
{
return spack<indxMax, seq...>{};
}
}
template <int indxMax, template <int...> class pack>
struct seq_pack
{
typedef decltype(get_seq17<indxMax, pack>()) seq;
};
//creating a sequence wrapper
template <int ... seq>
struct seqpack {};
//usage
seq_pack<4, seqpack>::seq; //seqpack<0, 1, 2, 3, 4>
Though this implementation is easier to understand, it is preferable to use std::make_index_sequence<Size>
as Julius has mentioned in the comments below.
DigitalEye
Updated on June 06, 2022Comments
-
DigitalEye almost 2 years
This below code from user Faheem Mitha, is based on user Johannes Schaub - litb's answer in this SO. This code perfectly does what I seek, which is conversion of a
tuple
into parameter pack, but I don't understand this code well enough and therefore I thought I will create a new discussion that might help template metaprogramming newbies like me. So, please pardon the duplicate posting.Now moving onto the code
#include <tuple> #include <iostream> using std::cout; using std::endl; template<int ...> struct seq {}; template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { }; template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; }; double foo(int x, float y, double z) { return x + y + z; } template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; double(*func)(Args...); double delayed_dispatch() { return callFunc(typename gens<sizeof...(Args)>::type()); // Item #1 } template<int ...S> double callFunc(seq<S...>) { return func(std::get<S>(params) ...); } }; int main(void) { std::tuple<int, float, double> t = std::make_tuple(1, (float)1.2, 5); save_it_for_later<int, float, double> saved = { t, foo }; cout << saved.delayed_dispatch() << endl; return 0; }
I'm completely confounded by Item #1 above:
- What purpose does
typename
serve on that line? - I understand that
gens<sizeof...(Args)>::type()
will expand togens<3>::type()
, but that doesn't seem to match neithertemplate<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { };
nortemplate<int ...S> struct gens<0, S...>
. I'm obviously missing the point and I'd be glad if someone can explain what is happening here.
I do understand that
callFunc
gets invoked in this formcallFunc(seq<0,1,2>)
and the return statement of this method itself expands toreturn func(std::get<0>(params), std::get<1>(params), std::get<2>(params)
and this is what makes this scheme work, but I cannot workout how thisseq<0,1,2>
type is generated.Note: Using
std::index_sequence_for
is not an option, my compiler doesn't support C++14 features.PS: Can this technique be classified as template metaprogramming?
- What purpose does