2014-09-05 150 views
56

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

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

当我编译失败,错误:

Error C3520: 'args': parameter pack must be expanded in this context

(在功能foo2)。

+1

如果'Args'为空? – CoffeeandCode 2014-09-05 07:28:15

回答

86

包扩展可能发生的地方之一是括号初始化列表。您可以通过将扩展虚拟阵列的初始化列表里利用这一点:

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 

Demo

扩大{}的一个重要优势是它保证了从左到右的评估。


用C++ 1Z fold expressions,你可以只写

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

我看到的一个变体是'using expander = int []; expander {...};'因为那么数组变量没有名字,编译器显然明白这个数组并不需要我创建或使用。 – 2014-09-05 16:20:15

+2

第二个'0'是什么?紧接逗号运算符 – 2015-07-27 21:20:35

+2

@AaronMcDaid后面的那个整数表达式的类型是int,并且与数组的元素类型匹配。 – 2015-07-27 21:22:02

36

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

经验法则是“扩张能够产生列表 - 分隔,模式其中,列表分隔符。”运算符,不构造语法意义上的列表。

要调用函数的每个参数,你可以使用递归(这是在可变参数模板编程的盒子的主要工具):

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

Live example

+2

该死的,你只是打赌我回答这个*摇晃拳头*,但你应该增加完美的转发给你的答案;这也是“可变模板程序员框中的主要工具”。 – CoffeeandCode 2014-09-05 07:26:01

+0

谢谢你的回答。我知道递归实现。我只想找到解决方法来编译没有递归和新函数的代码。 – 2014-09-05 07:26:04

+1

@ViacheslavDronov看起来好像你正在使用模板:你已经有一个由编译器生成的函数的垃圾负载,为什么不把它添加到该列表? – CoffeeandCode 2014-09-05 07:27:43

13

SHAMELESS COPY [approved by its source]

参数组可以只能在严格定义的上下文列表中扩展,并且运算符,不是其中之一。换句话说,不可能使用包扩展来生成由运算符,分隔的一系列子表达式组成的表达式。

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

要呼吁每一个参数的函数,你可以使用递归(这是在可变参数模板编程的盒子的主要工具):

#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++中,&&说明符可以表示以下两种情况之一:右值引用或通用引用。

我不会进入右值引用,但有人使用可变参数模板;普遍的参考文献是上帝派来的。

完美转发

一个std::forward和普遍引用的用途是类型的其他功能完美转发。

在您的例子,如果我们通过一个int&foo2它会自动降级为int,因为模板扣除后所产生的foo2功能的签名,如果你想要再往前这个arg到将修改它的另一个功能ny引用,你会得到不想要的结果(该变量不会被改变),因为foo2将通过传递一个int给它传递一个临时引用。为了解决这个问题,我们指定一个转发功能,将任意类型的参考转换为变量(右值为左值)。然后,为了确保我们传递的转发函数中传递的确切类型,我们使用std::forward,然后只有那么我们是否允许降级类型;因为我们现在处于最重要的地步。

如果需要,请阅读universal referencesperfect forwarding;斯科特迈尔斯作为一种资源相当出色。

1

可以使用make_tuple的包扩展,因为它引入了一个情境,其中通过膨胀产生的,序列有效

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

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

Demo

-1

执行顺序无法保证!

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

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

foo2(1, 2, 3, "3"); 

calling bar for 3 
calling bar for 3 
calling bar for 2 
calling bar for 1