2025年2月

折叠表达式(Fold Expression)是 C++17 引入的特性,用于简化对可变参数模板(Variadic Template)的操作。它通过将参数包(Parameter Pack)与运算符结合,避免了递归展开模板的繁琐代码。


基本语法

形式 语法 展开方式 示例

一元左折叠 (... op args) 左结合,按 ((a op b) op c) 展开
一元右折叠 (args op ...) 右结合,按 (a op (b op c)) 展开
二元左折叠 (init op ... op args) 左结合,带初始值 (0 + ... + args)(((0 + a) + b) + c)
二元右折叠 (args op ... op init) 右结合,带初始值 (args + ... + 0)(a + (b + (c + 0)))


示例代码

  1. 求和函数

    template<typename... Args>
    auto sum(Args... args) {
     return (args + ...); //右折叠 展开为:args1 + (args2 + (...))
    }
    
    int main() {
     std::cout << sum(1, 2, 3, 4); // 输出 10
    }
  2. 检查所有参数是否为真

    template<typename... Args>
    bool all_true(Args... args) {
     return (args && ...); //右折叠 展开为:args1 && args2 && ... && argsN
    }
    
    int main() {
     std::cout << std::boolalpha << all_true(true, true, false); // 输出 false
    }
  3. 拼接字符串

    template<typename... Args>
    std::string concat(Args... args) {
     return (std::string{} + ... + args); //左折叠 展开为:((str1 + str2) + str3)...
    }
    
    int main() {
     std::cout << concat("Hello", ", ", "C++", "!"); // 输出 "Hello, C++!"
    }

注意事项

  • 空参数包处理:空参数包需指定默认值,例如 (args + ... + 0)
  • 结合性差异:左折叠(从左侧开始计算)和右折叠(从右侧开始计算)可能因运算符特性导致结果不同。
  • 运算符限制:仅支持二元运算符,且需注意运算符优先级。

在C++11中引入了可变参数模板,顾名思义参数的数量是可以改变的,我们先来看一段代码

#include <iostream>
using namespace std;

void myPrint()
{
    cout << "the last one";
}

template <typename T, typename... Args>
void myPrint(T first, Args... rest)
{
    cout << "process:" << first << endl;
    myPrint(rest...);
}

int main()
{
    myPrint(123, "hello", 3.14);
    return 0;
}

输出

process:123
process:hello
process:3.14
the last one

我们慢慢分析这段代码,这段代码先是定义了一个myPrint函数,这个函数没有参数,当参数包为空时会调用该函数。
再是定义了一个函数模板,函数模板,模板参数中有一个typename... Args,这个参数是可变参数模板的关键。
在C++17之前可变参数模板是通过递归展开的。我们可以看到myPrint函数中调用了myPrint函数,这表明该可变参数函数模板通过递归的方式展开参数,而上述提到的无参数的函数模板就是该递归的终止条件。
对于递归展开的可变参数函数模板,必须确保每次递归参数包严格减少,必须定义终止函数即无参数同名函数。

上述代码的调用过程分析。

在main函数中我们给myPrint函数传入参数包{123,"hello",3.14}

  1. 第一次调用
    T=int,Args = {const char*,double},rest... = {"hello",3.14}
    cout<<123<<endl;
    myPrint("hello",3.14);
  2. 第二次调用
    T = const char*,Args = {double},rest... ={3.14}
    cout<<"hello"<<endl;
    myPrint(3.14);
  3. 第三次调用
    T = double,Args = {},rest... ={}
    cout<<3.14<<endl;
    myPrint();
  4. 第四次调用
    cout << "the last one";

    底层机制

    递归展开在编译期间生成多个函数实现:

  5. myPrint(int,const char*,double)
  6. myPrint(const char*,double)
  7. myPrint(double)
  8. myPrint()
    每个实例处理不同数量的类型。

    缺点

    参数包过大会导致编译时间加长,生成代码体积变大。

    其他方案

    在C++17以后可以使用折叠表达式简化代码,避免了现实递归。

    #include <iostream>
    using namespace std;
    
    template <typename... Args>
    void myPrint(Args... rest)
    {
     (cout <<...<<rest);
    }
    
    int main()
    {
     myPrint(123, "hello", 3.14);//输出123hello3.14
     return 0;
    }

    在下一篇文章中介绍折叠表达式