cpp - 折叠表达式

62 阅读3分钟

折叠表达式是C++引入的一个编译期计算特性,它允许在编译时对参数包进行展开和计算。这意味着所有的折叠表达式运算都是在程序编译阶段完成的,而不是在运行时进行计算,从而可以提供更好的性能。

语法

折叠表达式有下面四种语法。其中 op 代表操作符号,pack 代表传入的变量或者类型

// pack 表示为 (a1,a2,a3)

// 一元左折叠:从左到右计算
(... op pack)  // ((a1 op a2) op a3)

// 一元右折叠:从右到左计算
(pack op ...)  // (a1 op (a2 op a3))

// 二元左折叠:使用初始值
(init op ... op pack)  // (((init op a1) op a2) op a3)

// 二元右折叠:使用初始值
(pack op ... op init)  // (a1 op (a2 op (a3 op init)))

下面是上面四种语法的例子

// 1. 一元左折叠示例:从左到右计算所有数字的和
template<typename... Args>
auto sum_left(Args... args) {
    return (... + args);  // ((1 + 2) + 3) + 4
}
// 使用: sum_left(1, 2, 3, 4) 结果为 10

// 2. 一元右折叠示例:从右到左连接字符串
template<typename... Args>
std::string concat_right(Args... args) {
    return (args + ...);  // "a" + ("b" + ("c" + "d"))
}
// 使用: concat_right("a", "b", "c", "d") 结果为 "abcd"

// 3. 二元左折叠示例:带初始值的乘法
template<typename... Args>
auto multiply_left(Args... args) {
    return (1 * ... * args);  // ((1 * 2) * 3) * 4
}
// 使用: multiply_left(2, 3, 4) 结果为 24

// 4. 二元右折叠示例:带初始值的除法
template<typename... Args>
auto divide_right(Args... args) {
    return (args / ... / 2.0);  // 16 / (8 / (4 / 2.0))
}
// 使用: divide_right(16, 8, 4) 结果为 4.0

当 pack 是 时的处理规则

  • op 是 &&:求值为 true
  • op 是 ||:求值为 false
  • op 是 ,:求值为 void()
  • 其他操作符:未定义行为

使用

可变参数模板

允许模板接受任意数量的参数

template<typename... Args>  // Args 是一个模板参数包
auto sum(Args... args) {   // args 是一个函数参数包
    return (... + args);  // 一元左折叠
}

多重继承

允许模板接受任意数量的基类

// 多重继承示例
template<typename... Bases>
class Composite : public Bases... {
public:
    using Bases::operator()...;
};

#include <iostream>

// 使用示例
int main() {
    struct Adder {
        int operator()(int a, int b) const { return a + b; }
    };

    struct Multiplier {
        int operator()(float a, float b) const { return a * b; }
    };

    using Calculator = Composite<Adder, Multiplier>;
    Calculator calc;

    // 可以使用所有基类的operator()
    std::cout << calc(10, 5) << std::endl;  // 使用 Adder: 15
    std::cout << calc(10.f, 5.f) << std::endl;  // 使用 Multiplier: 50

    return 0;
}

非类型模板参数

允许模板接受任意数量的值

// 编译期计算数值和
template<int... Numbers>
struct Sum {
    static constexpr int value = (... + Numbers);
};

// 使用示例
static_assert(Sum<1, 2, 3, 4>::value == 10);

// 检查数组是否有序
template<int... Numbers>
struct IsSorted {
    static constexpr bool value = ((Numbers <= ...) <= Numbers);
};

// 使用示例
static_assert(IsSorted<1, 2, 3, 4>::value);      // true
static_assert(!IsSorted<1, 3, 2, 4>::value);     // false

类成员初始化中的省略号

可以用于重复初始化值

struct Point {
    int x, y, z;
};

// 所有成员使用相同的值初始化
Point p1{1, ...};     // 等同于 Point p1{1, 1, 1};

// 更复杂的例子
struct Matrix {
    int values[3][3];
};

Matrix m{{0, ...}, {1, ...}, {2, ...}};  // 每行用相同的值填充
// 等同于:
// Matrix m{{0, 0, 0}, {1, 1, 1}, {2, 2, 2}};

// 也可以用于数组
int arr[5]{42, ...};  // 等同于 int arr[5]{42, 42, 42, 42, 42};