C++ 17 折叠表达式

C++ 17 折叠表达式

C++17 引入的折叠表达式(Fold Expression)主要是给可变参数模板服务的。它的意义很直接:把一包参数按照某个运算符“折叠”成一个表达式,省掉以前那种递归展开模板的写法。

1. 为什么需要折叠表达式?

C++17 之前,如果我们想对一组参数求和,常见写法通常是递归模板:

1
2
3
4
5
6
7
8
9
10
int sum()
{
return 0;
}

template <typename T, typename... Args>
T sum(T first, Args... args)
{
return first + sum(args...);
}

这种写法能用,但比较啰嗦,而且可读性一般。
有了折叠表达式之后,代码可以直接写成:

1
2
3
4
5
template <typename... Args>
auto sum(Args... args)
{
return (... + args);
}

明显简洁很多。

2. 基本语法

折叠表达式分为四种形式:

1
2
3
4
( ... op pack )
( pack op ... )
( init op ... op pack )
( pack op ... op init )

其中:

  • pack:参数包
  • op:运算符
  • init:初始值

2.1 一元左折叠

1
(... + args)

等价于:

1
((arg1 + arg2) + arg3) + arg4

2.2 一元右折叠

1
(args + ...)

等价于:

1
arg1 + (arg2 + (arg3 + arg4))

对于加法这种结合性比较自然的运算,左右折叠结果通常一样;但对于减法这类运算,左右折叠就会不同。

3. 最常见的使用场景

3.1 求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

template <typename... Args>
auto sum(Args... args)
{
return (... + args);
}

int main()
{
cout << sum(1, 2, 3, 4) << endl;
return 0;
}

输出:

1
10

3.2 判断多个条件是否都成立

1
2
3
4
5
template <typename... Args>
bool allTrue(Args... args)
{
return (... && args);
}

使用起来很直观:

1
bool ret = allTrue(true, true, false);

3.3 依次输出多个参数

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

template <typename... Args>
void print(Args&&... args)
{
((cout << args << " "), ...);
}

调用:

1
print(1, "hello", 3.14);

4. 带初始值的折叠

有时候参数包可能为空,或者你希望给运算提供一个默认起点,这时可以使用带初始值的形式。

1
2
3
4
5
template <typename... Args>
auto sumWithInit(Args... args)
{
return (0 + ... + args);
}

这种写法在处理空参数包时更稳一些。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

template <typename... Args>
auto multiply(Args... args)
{
return (1 * ... * args);
}

int main()
{
cout << multiply(2, 3, 4) << endl;
return 0;
}

输出:

1
24

5. 可以使用哪些运算符?

折叠表达式支持的运算符并不只有 +。常见还有:

  • +
  • -
  • *
  • /
  • &&
  • ||
  • ,
  • <<
  • >>

其中 &&|| 在写条件组合时尤其方便。

6. 使用时要注意的点

6.1 左折叠和右折叠结果可能不同

例如减法:

1
2
3
4
5
6
7
8
9
10
11
template <typename... Args>
auto leftSub(Args... args)
{
return (... - args);
}

template <typename... Args>
auto rightSub(Args... args)
{
return (args - ...);
}

leftSub(10, 3, 2)rightSub(10, 3, 2) 的结果并不一样。

6.2 空参数包问题

不是所有运算符都适合在空参数包下直接折叠。
如果你不确定参数个数,最好提供初始值。

6.3 可读性优先

虽然折叠表达式很短,但如果你把很复杂的逻辑都塞进一行,代码也会变得难读。
它适合做“简单、重复、规则一致”的参数包处理。

总结

折叠表达式可以看作是 C++17 给可变参数模板补上的一块重要拼图。它让很多原本需要递归模板展开的代码,变成了更短、更直接的写法。
如果项目里经常要处理参数包,比如批量输出、批量判断、批量求和,那么这个特性会非常顺手。