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

In the context of variadic template, the ellipsis ... is used to unpack the template parameter pack if it appears on the right side of an expression (call this expression pattern for a moment), or it's a pack argument if it appears on left side of the name:

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

Hope that helps.


The following is taken from the talk "Variadic Templates are Funadic" by Andrei Alexandrescu at GoingNative 2012. I can recommend it for a good introduction on variadic templates.


There are two things one can do with a variadic pack. It's possible to apply sizeof...(vs) to get the number of elements and expand it.

Expansion rules

Use            Expansion

Ts...          T1, ..., Tn
Ts&&...        T1&&, ..., Tn&&
x<Ts,Y>::z...  x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>...   x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)...  func(5,v1), ..., func(5,vn)

Expansion proceeds inwards outwards. When expanding two lists in lock-step, they have to have the same size.

More examples:

gun(A<Ts...>::hun(vs)...);

Expands all Ts in the template argument list of A and then the function hun gets expanded with all vs.

gun(A<Ts...>::hun(vs...));

Expands all Ts in the template argument list of A and all vs as the function arguments for hun.

gun(A<Ts>::hun(vs)...);

Expands the function hun with Ts and vs in lock-step.

Note:

Ts is not a type and vs is not a value! They are aliases for a list of types/values. Either list may be potentially empty. Both obey only specific actions. So the following is not possible:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Expansion loci

Function arguments

template <typename... Ts>
void fun(Ts... vs)

Initializer lists

any a[] = { vs... };

Base specifiers

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Member initializer lists

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Tempate argument lists

std::map<Ts...> m;

Will only compile if there is a possible match for the arguments.

Capture lists

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Attribute lists

struct [[ Ts... ]] IAmFromTheFuture {};

It is in the specification, but there is no attribute that can be expressed as a type, yet.