Via*_*nov 69 c++ templates variadic-templates c++11
我正在尝试学习可变参数模板和函数.我无法理解为什么这段代码不能编译:
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;
}
Run Code Online (Sandbox Code Playgroud)
当我编译它失败时出现错误:
错误C3520:'args':必须在此上下文中扩展参数包
(在功能上foo2
).
T.C*_*.C. 113
可以发生包扩展的地方之一是在braced-init-list中.您可以通过将扩展放在虚拟数组的初始化列表中来利用此功能:
template<typename... Args>
static void foo2(Args &&... args)
{
int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}
Run Code Online (Sandbox Code Playgroud)
要更详细地解释初始化程序的内容:
{ 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
Run Code Online (Sandbox Code Playgroud)
演示.
扩展的一个重要优势{}
是它保证了从左到右的评估.
使用C++ 1z 折叠表达式,您只需编写即可
((void) bar(std::forward<Args>(args)), ...);
Run Code Online (Sandbox Code Playgroud)
Rei*_*ica 38
参数包只能在严格定义的上下文列表中进行扩展,而运算符,
不是其中之一.换句话说,不可能使用包扩展来生成由运算符分隔的一系列子表达式组成的表达式,
.
经验法则是"扩展可以生成一个分隔模式列表,,
其中,
是一个列表分隔符." 运算符,
不构造语法意义上的列表.
要为每个参数调用一个函数,可以使用递归(这是可变参数模板程序员框中的主要工具):
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");
}
Run Code Online (Sandbox Code Playgroud)
Cof*_*ode 15
参数包只能在严格定义的上下文列表中进行扩展,而运算符,
不是其中之一.换句话说,不可能使用包扩展来生成由运算符分隔的一系列子表达式组成的表达式,
.
经验法则是"扩展可以生成一个分隔,
模式列表,其中,
是一个列表分隔符." 运算符,
不构造语法意义上的列表.
要为每个参数调用一个函数,可以使用递归(这是可变参数模板程序员框中的主要工具):
#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");
}
Run Code Online (Sandbox Code Playgroud)
有用的非复制信息
你可能在这个答案中没有看到的另一件事是使用说明&&
符和std::forward
.在C++中,说明&&
符可以表示两种内容之一:rvalue-references或通用引用.
我不会进入rvalue-references,而是进入使用可变参数模板的人; 普遍的参考是神的发送.
完美的转发
std::forward
通用引用的一个用途是将类型转换为其他函数.
在你的例子中,如果我们传递int&
给foo2
它,它会自动降级为int
因为foo2
模板扣除后生成的函数的签名,如果你想arg
将它转发给另一个通过引用修改它的函数,你将得到不希望的结果(变量将不会被更改)因为foo2
将传递一个引用传递int
给它创建的临时变量.为了解决这个问题,我们指定了一个转发函数来对变量(rvalue 或 lvalue)进行任何类型的引用.然后,可以肯定的是我们传递我们使用转发功能传递的确切类型,然后和只有那么我们允许的类型贬降; 因为我们现在处于最重要的地步.std::forward
如果您需要,请阅读有关通用引用和完美转发的更多信息 ; Scott Meyers作为一种资源非常棒.
对此的 C++17 解决方案非常接近您的预期代码:
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;
}
Run Code Online (Sandbox Code Playgroud)
这在每个表达式之间使用逗号运算符扩展模式
// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));
Run Code Online (Sandbox Code Playgroud)