可变模板包扩展

我正在尝试学习可变参数模板和函数。 我不明白为什么这个代码不能编译:

template<typename T> static void bar(T t) {} template<typename... Args> static void foo2(Args... args) { (bar(args)...); } int main() { foo2(1, 2, 3, "3"); return 0; } 

当我编译失败,错误:

错误C3520:“args”:参数包必须在此上下文中展开

(在函数foo2 )。

其中一个包扩展可能发生的地方是在一个braced-init-list中 。 你可以利用这个扩展在虚拟数组的初始化列表中:

 template<typename... Args> static void foo2(Args &&... args) { int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... }; } 

要更详细地解释初始化程序的内容:

 { 0, ( (void) bar(std::forward<Args>(args)), 0) ... }; | | | | | | | | | --- pack expand the whole thing | | | | | | --perfect forwarding --- comma operator | | | -- cast to void to ensure that regardless of bar()'s return type | the built-in comma operator is used rather than an overloaded one | ---ensure that the array has at least one element so that we don't try to make an illegal 0-length array when args is empty 

演示 。

扩展{}一个重要优势是可以保证从左到右的评估。


用C ++ 1z 折叠expression式 ,你可以写

 ((void) bar(std::forward<Args>(args)), ...); 

参数包只能在严格定义的上下文列表中进行扩展,而操作符不在其中。 换句话说,使用包扩展来生成由操作符分隔的一系列子expression式组成的expression式是不可能的。

经验法则是“扩展可以生成一个,分离模式列表 ,其中,列表分隔符”。 运算符,不构造语法意义上的列表。

要为每个参数调用函数,可以使用recursion(这是可变参数模板程序员框中的主要工具):

 template <typename T> void bar(T t) {} void foo2() {} template <typename Car, typename... Cdr> void foo2(Car car, Cdr... cdr) { bar(car); foo2(cdr...); } int main() { foo2 (1, 2, 3, "3"); } 

现场示例

无暇拷贝[由其来源批准]

参数包只能在严格定义的上下文列表中进行扩展,而操作符不在其中。 换句话说,使用包扩展来生成由操作符分隔的一系列子expression式组成的expression式是不可能的。

经验法则是“扩展可以生成一个,分离模式列表,其中,是列表分隔符”。 运算符,不构造语法意义上的列表。

要为每个参数调用函数,可以使用recursion(这是可变参数模板程序员框中的主要工具):

 #include <utility> template<typename T> void foo(T &&t){} template<typename Arg0, typename Arg1, typename ... Args> void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ foo(std::forward<Arg0>(arg0)); foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...); } auto main() -> int{ foo(1, 2, 3, "3"); } 

有用的非复制信息

你可能还没有在这个答案中看到的另外一件事是使用&&指定符和std::forward 。 在C ++中, &&说明符可以表示2种东西之一:右值引用或通用引用。

我不会进入右值引用,而是使用可变参数模板的人; 普遍的引用是神派。

完美转发

std::forward和universal引用的用途之一是将types完美转发到其他函数。

在你的例子中,如果我们传递一个int&foo2 ,它将被自动降为int因为在模板推导之后生成的foo2函数的签名,如果你想将这个arg转发到另一个函数,它会修改它的引用,会得到不希望的结果(该variables不会被改变),因为foo2会传递一个引用到通过传递一个int创build的临时对象。 为了解决这个问题,我们指定一个转发函数来对variables(右值右值)进行任何types的引用 。 然后,为了确保我们传递了在转发函数中传递的确切types,我们使用std::forward ,然后允许降级types; 因为我们现在处于最重要的地步。

如果需要,请阅读更多关于通用参考和完美转发的内容 。 斯科特迈耶斯作为一个资源是相当不错的。

您可以使用make_tuple进行包扩展,因为它引入了由扩展生成的序列有效的上下文

 make_tuple( (bar(std::forward<Args>(args)), 0)... ); 

现在,我怀疑生成的零的未使用/未命名/临时元组可由编译器分离并优化

演示

执行顺序不能保证!

 make_tuple( (bar(std::forward<Args>(args)), 0)... ); 

在这个例子中,参数将按照与GCC至less相反的顺序打印。

 foo2(1, 2, 3, "3"); calling bar for 3 calling bar for 3 calling bar for 2 calling bar for 1