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