Is it possible to "store" a template parameter pack without expanding it?
Asked Answered
V

4

76

I was experimenting with C++0x variadic templates when I stumbled upon this issue:

template < typename ...Args >
struct identities
{
    typedef Args type; //compile error: "parameter packs not expanded with '...'
};

//The following code just shows an example of potential use, but has no relation
//with what I am actually trying to achieve.
template < typename T >
struct convert_in_tuple
{
    typedef std::tuple< typename T::type... > type;
};

typedef convert_in_tuple< identities< int, float > >::type int_float_tuple;

GCC 4.5.0 gives me an error when I try to typedef the template parameters pack.

Basically, I would like to "store" the parameters pack in a typedef, without unpacking it. Is it possible? If not, is there some reason why this is not allowed?

Vacillating answered 14/1, 2011 at 13:46 Comment(0)
H
57

Another approach, which is slightly more generic than Ben's, is as follows:

#include <tuple>

template <typename... Args>
struct variadic_typedef
{
    // this single type represents a collection of types,
    // as the template arguments it took to define it
};

template <typename... Args>
struct convert_in_tuple
{
    // base case, nothing special,
    // just use the arguments directly
    // however they need to be used
    typedef std::tuple<Args...> type;
};

template <typename... Args>
struct convert_in_tuple<variadic_typedef<Args...>>
{
    // expand the variadic_typedef back into
    // its arguments, via specialization
    // (doesn't rely on functionality to be provided
    // by the variadic_typedef struct itself, generic)
    typedef typename convert_in_tuple<Args...>::type type;
};

typedef variadic_typedef<int, float> myTypes;
typedef convert_in_tuple<myTypes>::type int_float_tuple;

int main()
{}
Hussy answered 14/1, 2011 at 16:42 Comment(7)
Very good workaround, I did not think about using partial template specialization!Vacillating
@GMan: quick question ... this was helpful, but should the partially specialized version actually be typedef typename convert_in_tuple<Args...>::type type;, or does that not matter?Dowling
@Jason: That's correct. I'm surprised my answer's gotten by so long without a keen eye noticing. :)Hussy
Baller solution! I love the madness that is C++.Slater
I'd be a bit concerned: while it is very tempting to say "treat a list of types, or an instance of a particular type that contains a list, as the same thing", in my experience things tend to explode messily when you do that. As an example, imagine a list of length 1 containing a variadic_typedef and how it interacts with the above code. Now imagine a list of types that are each passed into a convert_in_tuple and how it interacts with the above code. If you start setting up a few levels of indirection, treating the container and the contents as interchangable causes problems.Chloroform
I don't understand how this solves OP's problem. The convert_in_tuple struct contains an alias of an alias of a tuple. The type that it represents is a tuple with the Args ... parameter pack, and not the Args ... parameter pack itself.Thane
@user2813810: The thing representing the parameter pack itself is variadic_typedef, convert_in_tuple is just one example of how to get the parameter pack back out in a usable fashion.Stabilize
S
11

I think the reason it's not allowed is that it would be messy, and you can work around it. You need to use dependency inversion and make the struct storing the parameter pack into a factory template able to apply that parameter pack to another template.

Something along the lines of:

template < typename ...Args >
struct identities
{
    template < template<typename ...> class T >
    struct apply
    {
        typedef T<Args...> type;
    };
};

template < template<template<typename ...> class> class T >
struct convert_in_tuple
{
    typedef typename T<std::tuple>::type type;
};

typedef convert_in_tuple< identities< int, float >::apply >::type int_float_tuple;
Stabilize answered 14/1, 2011 at 14:31 Comment(6)
I tried your code on GCC 4.5, you just need to change typename T in class T and change the convert_in_tuple parameter to be a template template template parameter: template < template< template < typename ... > class > class T > struct convert_in_tuple {...} (!).Vacillating
@Luc: Edited to be a template template template parameter. Replacing typename with class feels a little dubious, since the draft says "There is no semantic difference between class and template in a template-parameter.", could you try this new code?Stabilize
I can't find it in the standard, but I think I remember that for template template parameters you need to use class and not typename (because a template type is inevitably a class and not any type).Vacillating
@Luc: Got it to compile in gcc 4.5.2 in a VM, thanks for the pointers. Now struggling to get copy+paste out of the VM to work...Stabilize
Indeed, the standard says in §14.1.2 that there is no difference between class and typename, but just above (in §14.1.1), the syntax only allows the class keyword in template template parameter declaration. Even though this can seem inconsistent, I think the rationale is, like I said before, that a template template parameter can't be any type (e.g. it can't be int or bool), so perhaps the committee decided that the use of typename would be misleading. Anyway, let's get back to the subject :)!Vacillating
Your solution is nice, but it sad that we need to employ such workarounds...However, it is true that supporting this in a clean way could have been a bit hard, it would have meant that a typedef could be either a plain type or a parameter pack, which is weird...Perhaps a new syntax could have been used, e.g. a 'packed typedef' (typedef ...Args args or something along this way).Vacillating
S
3

I've found Ben Voigt's idea very useful in my own endeavors. I've modified it slightly to make it general to not just tuples. To the readers here it might be an obvious modification, but it may be worth showing:

template <template <class ... Args> class T, class ... Args>
struct TypeWithList
{
  typedef T<Args...> type;
};

template <template <class ... Args> class T, class ... Args>
struct TypeWithList<T, VariadicTypedef<Args...>>
{
  typedef typename TypeWithList<T, Args...>::type type;
};

The name TypeWithList stems from the fact that the type is now instantiated with a previous list.

Syblesybley answered 4/9, 2013 at 10:56 Comment(0)
S
2

This is a variation of GManNickG's neat partial specialization trick. No delegation, and you get more type safety by requiring the use of your variadic_typedef struct.

#include <tuple>

template<typename... Args>
struct variadic_typedef {};

template<typename... Args>
struct convert_in_tuple {
    //Leaving this empty will cause the compiler
    //to complain if you try to access a "type" member.
    //You may also be able to do something like:
    //static_assert(std::is_same<>::value, "blah")
    //if you know something about the types.
};

template<typename... Args>
struct convert_in_tuple< variadic_typedef<Args...> > {
    //use Args normally
    typedef std::tuple<Args...> type;
};

typedef variadic_typedef<int, float> myTypes;
typedef convert_in_tuple<myTypes>::type int_float_tuple; //compiles
//typedef convert_in_tuple<int, float>::type int_float_tuple; //doesn't compile

int main() {}
Smasher answered 18/2, 2013 at 1:8 Comment(3)
There was no recursion in @GManNickG's answer, and I think the ability of using a raw parameter pack instead of variadic_typedef was meant to be a feature. Hence, I would say this answer is more a degradation than a refinement...Vacillating
I understand that the option of not using a variadic_typedef was intended to be a feature, but one man's feature is another man's bug. The reason I was on this thread in the first place was to find a way to do exactly what my answer does here. Also, there is a single recursive "call" in @GManNickG's solution - when the variadic_typdef partial specialization of convert_in_tuple "delegates" to the unspecialized version. Without it, things are slightly simpler. And lastly, I chose the word refinement not to cast my solution as better, but as more specific. I changed my wording to reflect this.Smasher
You can remove dependency on variadic_typedef for convert_in_tuple -- have it take template<typename Pack> struct convert_in_tuple {};, then specialize template<template<typename...>class Pack, typename...Args> struct convert_in_tuple<Pack<Args...>> { typedef std::tuple<Args> type; } -- now any variardic pack can be mapped to a tuple.Chloroform

© 2022 - 2024 — McMap. All rights reserved.