一、解决什么问题?
问题:在 C++17 之前,你想对可变参数包做一个"累加"操作,必须写递归模板:
// C++11 风格的求和(痛苦)
auto sum() { return 0; } // 终止
template <typename T, typename... Args>
auto sum(T first, Args... rest) {
return first + sum(rest...); // 递归
}
折叠表达式:一句话搞定。
二、四种折叠——看明白一张表就够了
| 语法 | 名称 | 对 (1, 2, 3, 4) 的展开 |
|---|---|---|
(args + ...) |
一元右折叠 | (1 + (2 + (3 + 4))) |
(... + args) |
一元左折叠 | (((1 + 2) + 3) + 4) |
(0 + ... + args) |
二元右折叠 | (0 + (1 + (2 + 3))) |
(args + ... + 0) |
二元左折叠 | (((1 + 2) + 3) + 0) |
要不要区分左和右?
对于加法,左右没区别(加法可交换)。但对于减法、字符串拼接等不可交换的操作,顺序决定结果:
// 左折叠:((1 - 2) - 3) - 4 = -8
template <typename... Args>
auto left_sub(Args... args) { return (... - args); }
// 右折叠:1 - (2 - (3 - 4)) = -2
template <typename... Args>
auto right_sub(Args... args) { return (args - ...); }
left_sub(1, 2, 3, 4); // -8
right_sub(1, 2, 3, 4); // -2
三、两种最常用的实际场景
场景 1:全部满足某个条件(和 std::conjunction 一样)
template <typename... Args>
bool all_true(Args... args) {
return (... && args); // (true && (true && false)) → false
}
all_true(true, true, true); // true
all_true(true, true, false); // false
场景 2:批量打印到流
template <typename... Args>
void print(std::ostream& os, Args... args) {
// 逗号表达式折叠:依次执行 (os << arg) 并忽略结果
((os << args << " "), ...);
}
print(std::cout, 1, 2.0, "hello"); // 输出 "1 2 hello "
这里的关键是逗号操作符——在 C++ 中,(expr1, expr2) 先执行 expr1,再执行 expr2,返回 expr2 的值。折叠逗号表达式就是"依次执行每个表达式"。
四、面试题:用折叠表达式实现编译期字符串哈希
// 编译期计算 "hello" 的哈希值
template <typename... Args>
constexpr size_t hash_string(Args... args) {
// 对参数包中的每个字符做哈希运算
size_t h = 0;
// 注意这里要用 ( , ...) 折叠
((h = h * 31 + static_cast<size_t>(args)), ...);
return h;
}
// 使用
constexpr auto h = hash_string('h', 'e', 'l', 'l', 'o');
// 编译器直接算出哈希值 99162322