使用逗号运算符和可变模板参数包折叠表达式

Fold expression with comma operator and variadic template parameter pack

#include<iostream>
using namespace std;

template<typename ...Args>
void output_argus(Args&&... args) 
{
    ((cout << args << '\n'), ...);    // #1
    (... , (cout << args << '\n'));   // #2
}


int main()
{
    output_argus(1, "test", 5.6f);
}

基于 c++ 运算符 doc',' 是一个 从左到右的运算符 。它的意思是 a, b, c, d,意思是 (((a, b), c),d) 而不是 (a, (b, (c, d)))。如果 a、b、c、d 是语句,这很重要。

然而,基于折叠表达式doc,对于','应该使用一元左折叠

我的问题是为什么我的代码中的两个语句都有效?不应该只有#2 工作吗? 还有如何理解...args。和嵌套折叠表达式?

在链接页面中,您的#1 扩展如下:

((cout << args₀ << '\n'), ((cout << args₁ << '\n'), (cout << args₂ << '\n')));

删除一些重复以使其更清晰:

args₀, (args₁, args₂)

对于#2,扩展归结为:

(args₀, args₁), args₂

让我们来看看每一个的评价。

#1:

args₀  ,  (args₁, args₂)
      ^^^

带下划线的逗号计算左侧,打印 1。然后右边求值,它求值args₁的打印,打印test,然后是args₂的打印,打印5.6.

#2:

(args₀, args₁)  ,  args₂
               ^^^

带下划线的逗号计算左侧。这会触发另一个逗号的计算,它计算 args₀ 的打印,打印 1,然后计算 args₁ 的打印,打印 test。现在,带下划线的逗号已完成评估左侧并评估右侧,打印 5.6.

如您所见,尽管括号的分组不同,但两者对每个参数的求值顺序相同。

请注意,一般来说,这可能并不总是成立。一些运算符,例如 +,不像逗号运算符那样有保证的评估顺序。如果使用这样的运算符而不是逗号来连接打印表达式,编译器最终可以选择以任何顺序评估各个参数打印。

假设我们在一个二元运算符上折叠 3 个表达式,使用一元折叠。我们这里有两个选项:(xs @ ...)(一元右折叠)和 (... @ xs)(一元左折叠)。

(xs @ ...) 扩展为 (a @ (b @ c))

(... @ xs) 扩展为 ((a @ b) @ c)

关于表达式 a @ (b @ c)(a @ b) @ c 之间的区别,我们能说些什么?如果 @ 在这些类型上是 associative,那么这两个表达式是相同的。这就是联想的意思。如果您有一个整数参数包,那么一元左折叠 + 和一元右折叠 + 将具有相同的值(模数溢出),因为加法是关联的。另一方面,减法不是结合的。 (xs - ...)(... - xs) 意思完全不同。

同样,C++ 中的 , 运算符是结合运算符。将表达式括起来的方式无关紧要。 ((a, b), c)(a, (b, c)) 都计算并丢弃 a,然后计算并丢弃 b,然后计算 c,这就是结果。如果将表达式简化为字母,就更容易看出为什么会这样。

因此,((cout << args << '\n'), ...)(... , (cout << args << '\n')) 做同样的事情,并且它们都有效地表示:

cout << args1 << '\n';
cout << args2 << '\n';
// ...
cout << argsN << '\n';