101

在 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

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

4

2 回答 2

103

在可变参数模板的上下文中,省略号...用于解包模板参数包,如果它出现在表达式的右侧(暂时称为此表达式模式),或者如果它出现在左侧,则它是一个包参数姓名:

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

规则是重复左侧的任何模式- 解压缩的模式(现在称为表达式)用逗号分隔。...,

通过一些例子可以最好地理解它。假设你有这个函数模板:

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

T现在,如果我将此函数调用为传递{int, char, short},则每个函数调用都扩展为:

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) );

在您发布的代码中,std::forward遵循n()函数调用说明的第四种模式。

x(args)...注意和上面的区别y(args...)


您也可以使用...以下方式初始化数组:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

扩展为:

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

我刚刚意识到一个模式甚至可以包含访问说明符,例如public,如下例所示:

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

在此示例中,模式扩展为:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

也就是说,从所有基类公开mixture派生。

希望有帮助。

于 2013-07-15T11:00:53.723 回答
49

以下内容摘自 Andrei Alexandrescu 在 GoingNative 2012 上的演讲“Variadic Templates are Funadic”。我可以推荐它来很好地介绍可变参数模板。


使用可变参数包可以做两件事。可以申请sizeof...(vs)获取元素的数量并进行扩展。

扩展规则

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)

膨胀由内向外进行。当同步扩展两个列表时,它们必须具有相同的大小。

更多示例:

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

Ts在模板参数列表中展开 all A,然后函数hun用 all 展开vs

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

扩展Ts模板参数列表中的Aall 和 allvs作为 的函数参数hun

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

hun使用Ts和同步扩展功能vs

笔记:

Ts不是类型vs也不是值!它们是类型/值列表的别名。任一列表都可能为空。两者都只服从特定的行动。所以以下是不可能的:

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

扩展位点

函数参数

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

初始化器列表

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

基本说明符

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

成员初始化器列表

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

模板参数列表

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

只有当参数可能匹配时才会编译。

捕获列表

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

属性列表

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

它在规范中,但还没有可以表示为类型的属性。

于 2013-07-15T11:46:46.037 回答