文档目录

一、解决什么问题?

问题:在 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