可变模板包扩展
我正在尝试学习可变参数模板和函数。 我不明白为什么这个代码不能编译:
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