**C++17中的折叠表达式:如何轻松实现可变参数运算**

折叠表达式(fold expression)是 C++17 引入的一项强大功能,它可以让我们在函数模板中对任意数量的参数执行归约操作。相比手写递归或使用 std::accumulate 的变体,折叠表达式更简洁、更易读且性能更好。下面让我们从概念到实际案例,逐步掌握折叠表达式的使用方法。


1. 折叠表达式的基本语法

折叠表达式可以是三种形式:

  1. 左折叠
    (... op pack)
    先从最左边开始折叠,例如 (... + args) 对应 (((a + b) + c) + d)

  2. 右折叠
    (pack op ...)
    先从最右边开始折叠,例如 (args + ...) 对应 ((a + b) + c) + d)

  3. 双折叠
    (... op pack op ...)
    两端都折叠,常用于需要在左侧和右侧都加入初始值的情况,例如 (0 + ... + args)

在使用折叠表达式时,op 必须是一个二元运算符(如 +, *, &&, || 等),而 pack 是可变参数包。


2. 经典示例:可变参数求和

#include <iostream>

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

int main() {
    std::cout << sum(1, 2, 3, 4, 5) << '\n'; // 输出 15
}

这里 (args + ...) 生成 (((1 + 2) + 3) + 4) + 5 的递归实现,省去了显式的 std::initializer_list 或循环。


3. 复杂运算:多重折叠

假设我们想计算一组整数的乘积,然后再对结果进行取模运算。可以组合折叠表达式:

template<typename T, typename... Args>
T product_mod(T mod, Args... args) {
    return (static_cast <T>(args) * ... * mod) % mod;
}

调用:

int main() {
    std::cout << product_mod(1000, 3, 5, 7); // 105 % 1000 = 105
}

注意这里使用了 `static_cast

(args)` 确保所有参数转换为同一类型,以避免隐式类型提升导致的错误。 — ### 4. 逻辑折叠:判断所有参数是否为真 “`cpp template bool all_true(Args… args) { return (args && …); // 左折叠 } “` 使用场景:验证一组配置是否全部满足条件。 — ### 5. 自定义折叠:使用结构体包装 如果想在折叠表达式中使用自定义操作(非运算符),可以通过结构体包装实现: “`cpp struct And { template constexpr bool operator()(T&& t, U&& u) const noexcept { return std::forward (t) && std::forward(u); } }; template bool all_true_custom(Args… args) { return (… && args); // 仍然可以使用 &&,但如果想使用 And,可以改写为: // return (And{}(args, …)); } “` — ### 6. 与 `std::initializer_list` 对比 折叠表达式与 `std::initializer_list` 的区别: | 特点 | `std::initializer_list` | 折叠表达式 | |——|————————|————| | 语法 | `int sum = (int){a,b,c}.size();` | `return (a + b + c);` | | 可变参数 | 需要通过函数模板显式展开 | 直接使用 `…` | | 性能 | 可能产生临时对象 | 直接生成归约操作 | | 适用范围 | 只能处理固定类型 | 可以是任何类型且支持二元运算 | — ### 7. 实战:实现 `apply` 与 `pipe` 折叠表达式也可以用来实现函数组合工具: “`cpp template auto pipe(F f, Args&&… args) { return (… (f(std::forward (args)))); // 先执行 f(args1),再 f(args2) … } “` 示例: “`cpp auto square = [](int x){ return x * x; }; int result = pipe(square, 1, 2, 3); // 结果为 36 “` 这里先对 1 进行平方得到 1,然后对 2 进行平方得到 4,最后对 3 进行平方得到 9,最终将 1、4、9 用 `+` 折叠得到 14(如果我们改写为 `(…) + …` 则得到 14)。 — ### 8. 小结 – 折叠表达式提供了对可变参数包的强大归约能力,写法简洁且语义明确。 – 通过左折叠、右折叠和双折叠,可以完成加法、乘法、逻辑判断等多种归约操作。 – 结合模板编程,折叠表达式可以实现高度抽象且高效的通用工具。 – 相比 `std::initializer_list` 或手写递归,折叠表达式在性能与可读性上都有显著优势。 掌握折叠表达式后,你可以在日常 C++ 开发中轻松实现各种可变参数运算,从而让代码更优雅、更高效。

发表评论