Variadic模板包扩展

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)

  • 我见过的一个变体是`使用expandder = int []; expandder {...};`因为那时数组变量没有名称,并且编译器显然不需要创建或使用该数组. (17认同)
  • 什么是第二个'0`?刚刚在逗号运算符之后的那个 (3认同)
  • @AaronMcDaid这样整个表达式的类型是`int`并匹配数组的元素类型. (3认同)
  • @AaronMcDaid [`(void)`永远不会调用`operator void()`](http://stackoverflow.com/questions/4031228/why-is-operator-void-not-invoked-with-cast-syntax)(谢天谢地) ). (2认同)

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)

实例

  • 您可以使用虚拟阵列轻松进行扩展.`int dummy [] = {0,((void)bar(std :: forward <Args>(args)),0)...};`. (5认同)
  • 该死的,你只是打赌我回答这个*摇拳*但你应该为你的答案添加完美的转发; 这也是"可变参数模板程序员框中的主要工具". (2认同)

Cof*_*ode 15

SHAMELESS COPY [来源批准]

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

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

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

#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作为一种资源非常棒.


Gui*_*cot 8

对此的 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)