在 C++20 中,constexpr 和 consteval 都允许在编译时进行计算,但它们的语义和使用场景有显著差异。下面我们从概念、限制、典型用例以及性能影响四个角度来剖析这两者,帮助你在编写高效、可读、可靠的 C++ 代码时做出正确选择。
1. 基本定义
| 关键词 | 允许的上下文 | 计算时机 | 运行时可用性 |
|---|---|---|---|
constexpr |
变量、函数、构造函数、类成员、模板非类型参数等 | 既可在编译时也可在运行时 | 运行时可见 |
consteval |
函数(不包括构造函数) | 必须在编译时 | 仅编译时可见 |
constexpr语义:若在编译时可以完成计算,则在编译时计算;否则在运行时计算。编译器会根据调用位置决定是否使用编译时执行。consteval语义:调用点必须满足“必须在编译时求值”。若编译器无法在编译时计算,程序即编译失败。
2. 语法与约束
2.1 constexpr
constexpr int square(int x) {
return x * x;
}
- 允许递归、循环(C++20 引入
constexpr循环),但必须满足所有语句在编译时可求值。 - 对象可声明为
constexpr,但需要满足构造函数也为constexpr。 - 可以用作非类型模板参数。
2.2 consteval
consteval int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
- 只能是函数(不能是变量、构造函数或类成员)。C++20 之前没有此限制,但标准已将其明确为仅函数。
- 所有参数都必须在编译时可用(不允许传递运行时变量)。
- 不允许递归调用返回值未在编译时完成时导致无限递归。
3. 典型用例
3.1 何时使用 constexpr
| 场景 | 说明 |
|---|---|
| 可变计算 | 需要在运行时或编译时都可能出现的数值,例如从配置文件读取并且在编译时有默认值。 |
| 模板非类型参数 | 如 std::array<int, N>,N 必须是 constexpr 表达式。 |
| 需要兼容旧编译器 | constexpr 在 C++11 就已存在,consteval 仅在 C++20。 |
| 允许运行时使用 | 例如 constexpr std::string_view 在编译时生成常量,但在运行时也可使用。 |
3.2 何时使用 consteval
| 场景 | 说明 |
|---|---|
| 强制编译时求值 | 防止误用导致运行时错误,例如 constexpr 的函数在运行时可能被调用,使用 consteval 可以立即报错。 |
| 编译时验证 | 用于元编程,确保输入合法,例如编译时验证模板参数是否合法。 |
| 性能敏感 | 对于极其小而频繁调用的函数,使用 consteval 可避免潜在的运行时开销,尽管编译器通常会内联 constexpr。 |
| 静态编译期检查 | 用于实现像 static_assert 那样的检查,但可以返回值并被直接使用。 |
4. 性能与可维护性对比
- 性能:大多数现代编译器会在可能的情况下将
constexpr函数内联并在编译时求值。区别主要在于是否强制编译时计算。若编译器无法在编译时求值,constexpr可能导致运行时计算,consteval则会导致编译错误。使用consteval时要确保所有调用点都满足编译时可求值,否则将导致编译失败。 - 可维护性:
constexpr语义更宽松,代码更易被重用。consteval提供了更强的安全保障,适合库设计者想要确保某些接口只能在编译期使用的场景。
5. 实际案例
5.1 用 consteval 做数组大小检查
consteval std::size_t array_size(std::size_t n) {
if (n == 0) throw "Size must be > 0";
return n;
}
template<std::size_t N>
struct FixedBuffer {
std::array<char, array_size(N)> buf;
};
任何 N==0 的实例都会在编译时报错。
5.2 用 constexpr 计算数学常量
constexpr double pi() {
return 3.14159265358979323846;
}
double circumference(double radius) {
return 2 * pi() * radius; // 运行时可用
}
此函数在运行时可以接受任意 radius,但 pi() 本身在编译时已求值。
6. 小结
constexpr:适用于可在编译时也可在运行时求值的函数或变量,兼容性好,使用范围广。consteval:强制编译时求值,适用于需要在编译期验证或避免运行时开销的场景。- 选择时请根据函数的调用位置、可用性和安全性需求做决定。
通过正确使用 constexpr 与 consteval,可以在 C++20 及以后版本中编写更安全、更高效且易维护的代码。