在 C++11 引入 constexpr 的时候,它的功能仅仅是让函数在编译期可求值,且只能包含单一 return 语句、没有循环或递归等复杂控制流。随后 C++14 扩大了其能力,允许在 constexpr 函数中使用变量声明、循环和递归,甚至支持异常处理。C++17 进一步改进,允许 constexpr 变量在声明时进行初始化,同时支持非静态数据成员的 constexpr 初始化。到 C++20,constexpr 彻底变成了“常量表达式的通用工具”,任何在编译期能得到值的表达式都可以用 constexpr 修饰,甚至可以在类模板内部使用 constexpr 产生编译期生成的成员。C++23 则将 constexpr 函数的体裁进一步宽松到几乎任何合法的函数体,并允许在 constexpr 中使用动态异常处理、虚函数调用等,极大提升了其表达力。
以下代码展示了从 C++11 到 C++23 对 constexpr 的演进,并阐述了它在实际项目中的应用场景。
#include <iostream>
#include <array>
#include <cmath>
#include <concepts>
#include <utility>
// C++11 constexpr:单一 return 语句
constexpr int square_old(int x) {
return x * x;
}
// C++14 constexpr:循环、递归
constexpr int factorial(int n) {
return n <= 1 ? 1 : (n * factorial(n - 1));
}
// C++17 constexpr:在类内部使用
struct Matrix {
constexpr Matrix(int rows, int cols) : rows(rows), cols(cols), data(rows * cols) {}
constexpr double& operator()(int r, int c) { return data[r * cols + c]; }
int rows, cols;
std::vector <double> data;
};
// C++20 constexpr:constexpr 变量、泛型
template <typename T>
constexpr T max_cpp20(const T& a, const T& b) {
return a > b ? a : b;
}
// C++23 constexpr:支持异常、虚函数
class Base {
public:
virtual constexpr double get_value() const = 0;
};
class Derived : public Base {
public:
constexpr double get_value() const override { return 42.0; }
};
int main() {
constexpr int sq = square_old(5); // 编译期求值
constexpr int fact = factorial(5); // 递归编译期计算
constexpr Matrix m(2, 3); // 结构体 constexpr 构造
m(0, 1) = 3.14; // 运行期修改
constexpr double val = max_cpp20(3.14, 2.71); // 模板 constexpr
constexpr Derived d;
constexpr double derived_val = d.get_value(); // 虚函数 constexpr
std::cout << "square: " << sq << "\n";
std::cout << "factorial: " << fact << "\n";
std::cout << "matrix[0][1]: " << m(0, 1) << "\n";
std::cout << "max: " << val << "\n";
std::cout << "derived value: " << derived_val << "\n";
}
主要收益
- 编译期安全:constexpr 让常量表达式在编译期求值,避免了运行时错误。例如,
std::array的大小可以直接由 constexpr 计算得到,从而在编译阶段检查边界错误。 - 性能提升:对经常使用的计算(如多项式、斐波那契数列、数学常数等)使用 constexpr 可以消除运行时计算。
- 模板元编程的简化:传统的元编程往往依赖于
std::integral_constant、std::enable_if等技术,constexpr 的普及让函数式编程成为可能,代码更加直观。 - 更好的错误诊断:如果 constexpr 计算失败,编译器会报错,帮助开发者在编译期捕捉错误,而不是等到运行时。
注意事项
- 过度使用 constexpr 可能导致编译时间显著增加,尤其是在包含大量递归或循环的 constexpr 函数时。
- constexpr 变量必须在声明时完成初始化,且初始化值必须是常量表达式。
- 虽然 C++23 允许在 constexpr 中使用虚函数,但该调用仍会在编译期被解析为静态多态,运行时并不会产生虚调用开销。
通过合理规划 constexpr 的使用,能够让 C++ 代码更安全、更高效、更易维护。