在可变模板的上下文中,“ ...”标记的规则是什么?

在 C + + 11中有这样的可变模板:

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

这里有一些奇怪的地方: 表达式 std::forward<Args>(args)...同时使用 Argsargs,但是只有一个 ...标记。此外,std::forward是一个非可变的模板函数,只有一个模板参数和一个参数。这个问题(大致)的语法规则是什么?它怎么能被推广呢?

另外: 在函数实现中,省略号(...)位于感兴趣的表达式的末尾。在模板参数列表和参数列表中省略号位于中间是否有原因?

4298 次浏览

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:

  • it unpacks 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.