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

22,711

Solution 1

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()
{}

Solution 2

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;

Solution 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.

Solution 4

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() {}
Share:
22,711
Geekoder
Author by

Geekoder

I am a doctor in software engineering, working as a senior software developer for Allegorithmic.

Updated on July 05, 2022

Comments

  • Geekoder
    Geekoder almost 2 years

    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?

  • Geekoder
    Geekoder over 13 years
    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 {...} (!).
  • Ben Voigt
    Ben Voigt over 13 years
    @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?
  • Geekoder
    Geekoder over 13 years
    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).
  • Ben Voigt
    Ben Voigt over 13 years
    @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...
  • Geekoder
    Geekoder over 13 years
    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 :)!
  • Geekoder
    Geekoder over 13 years
    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).
  • Geekoder
    Geekoder over 13 years
    Very good workaround, I did not think about using partial template specialization!
  • Jason
    Jason over 12 years
    @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?
  • GManNickG
    GManNickG over 12 years
    @Jason: That's correct. I'm surprised my answer's gotten by so long without a keen eye noticing. :)
  • Casey Rodarmor
    Casey Rodarmor about 12 years
    Baller solution! I love the madness that is C++.
  • Geekoder
    Geekoder about 11 years
    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...
  • jack
    jack about 11 years
    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.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 10 years
    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.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont over 10 years
    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.
  • JPHarford
    JPHarford over 9 years
    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.
  • Ben Voigt
    Ben Voigt about 4 years
    @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.