c++c++11variadic-templatespack-expansion

What are the rules for the "..." token in the context of variadic templates?


In C++11 there are variadic templates like this one:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

There are some curiosities about this: The expression std::forward<Args>(args)... uses both Args and args but only one ... token. Furthermore std::forward is a non-variadic template function taking only one template parameter and one argument. What are the syntax rules for that (roughly)? How can it be generalized?

Also: In the function implementation the ellipsis (...) is at the end of the expression of interest. Is there a reason that in the template argument list and the parameter list the ellipsis is in the middle?


Solution

  • In the context of variadic template, the ellipsis ... is used to either pack or unpack parameters/arguments in a template definition, depending on the relative position where it appears:

    ...thing  // pack   : appears as template arguments
    thing...  // unpack : appears when consuming the arguments
    

    The rule is that whatever pattern is on the left side of ... is repeated — the unpacked patterns (call them expressions now) are separated by comma ,.

    It can be best understood by some examples. Suppose you have this function template:

    template <typename ...T> //pack
    void f(T ...args)        //pack
    {
       // here are unpack patterns
    
       g( args... );         //pattern = args
       h( x(args)... );      //pattern = x(args)
       m( y(args...) );      //pattern = args (as argument to y())
       n( z<T>(args)... );   //pattern = z<T>(args)
    }
    

    Now if I call this function passing T as {int, char, short}, then each of the function call is expanded as:

    g( arg0, arg1, arg2 );           
    h( x(arg0), x(arg1), x(arg2) );
    m( y(arg0, arg1, arg2) );
    n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );
    

    In the code you posted, std::forward follows the fourth pattern illustrated by n() function call.

    Note the difference between x(args)... and y(args...) above!


    You can use ... to initialize an array also as:

    struct data_info
    {
         boost::any  data;
         std::size_t type_size;
    };
    
    std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}
    

    which is expanded to this:

    std::vector<data_info> v 
    { 
       {arg0, sizeof(int)},
       {arg1, sizeof(char)},
       {arg2, sizeof(short)}
    };
    

    I just realized a pattern could even include access specifier such as public, as shown in the following example:

    template <typename ...Mixins>
    struct mixture : public Mixins...  //pattern = public Mixins
    {
        //code
    };
    

    In this example, the pattern is expanded as:

    struct mixture__instantiated :
        public Mixin0, public Mixin1, /* […] */ public MixinN
    

    That is, mixture derives publicly from all the base classes.