如何使用包扩展循环遍历参数包?

问题描述 投票:0回答:6

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

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
中)。

c++ c++11 templates variadic-templates pack-expansion
6个回答
157
投票

可能发生包扩展的地方之一是在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++17折叠表达式,你可以直接写

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

43
投票

参数包只能在严格定义的上下文列表中扩展,而运算符

,
不是其中之一。换句话说,不可能使用包扩展来生成由一系列由运算符
,
分隔的子表达式组成的表达式。

经验法则是“扩展可以生成由 , 分隔的模式组成的

list
,其中
,
list 分隔符。”运算符
,
并不构造语法意义上的列表。

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

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

实例


18
投票

无耻复制[经其来源批准]

参数包只能在严格定义的上下文列表中扩展,而运算符

,
不是其中之一。换句话说,不可能使用包扩展来生成由一系列由运算符
,
分隔的子表达式组成的表达式。

经验法则是“扩展可以生成由

,
分隔的模式列表,其中
,
是列表分隔符。”运算符
,
并不构造语法意义上的列表。

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

#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
到另一个通过引用修改它的函数,您将得到不需要的结果(变量不会改变),因为
foo2
将传递对通过向其传递
int
创建的临时对象的引用。为了解决这个问题,我们指定一个转发函数来将 any 类型的 reference 传递给变量(右值 左值)。然后,为了确保我们传递在转发函数中传递的确切类型,我们使用
std::forward
,然后并且only然后我们允许类型降级;因为我们现在正处于最重要的时刻。

如果需要,请阅读更多关于通用参考完美转发;斯科特·迈耶斯(Scott Meyers)作为资源非常出色。


12
投票

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

这会在每个表达式之间使用逗号运算符扩展模式

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));

4
投票

您可以使用

make_tuple
进行包扩展,因为它引入了一个上下文,其中扩展生成的
,
序列是有效的

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

现在,我怀疑生成的未使用/未命名/临时零元组可以被编译器检测到并被优化掉。

演示


1
投票

这是一个完整的示例,基于此处的答案。

重现

console.log
的示例,如 JavaScript 中所示:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

文件名例如

js_console.h

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};
© www.soinside.com 2019 - 2024. All rights reserved.