在 C++20 之前,constexpr 是实现编译期计算的主要手段。随着 C++20 的到来,consteval 作为一种新的关键字被引入,用于强制表达式在编译期求值。两者看似相似,却有不同的语义、适用场景和限制。下面从定义、用途、编译期行为以及实际使用的最佳实践四个角度对它们进行对比,并给出实际示例,帮助你在项目中做出更合适的选择。
1. 基本定义
| 关键字 | 说明 | 编译期求值 |
|---|---|---|
constexpr |
声明函数或变量可以在编译期求值,若满足约束则可以在编译期执行 | 可选,满足条件时可在编译期求值 |
consteval |
声明函数或变量必须在编译期求值 | 必须在编译期求值,编译器会报错如果无法在编译期完成 |
注意:
constexpr不是consteval的子集。consteval要求 强制 编译期求值,而constexpr则允许在需要时退回到运行时。
2. 典型使用场景
constexpr
- 常量表达式:在数组大小、模板参数等处需要编译期常量。
- 延迟求值:允许函数在运行时被调用,若在编译期可以求值则进行求值。
- 跨 C++ 标准兼容:在 C++17 及之前版本可用,兼容更广泛的编译器。
consteval
- 编译期错误检查:当某些错误只能在编译期发现时,使用
consteval可确保程序在编译阶段即报错。 - 强制生成静态断言:例如
consteval int factorial(int n) { static_assert(n <= 10, "Too large"); ... }。 - 模板元编程的前置验证:对模板参数做严格检查,防止无效实例化。
3. 语义细节与限制
-
函数返回类型
constexpr函数的返回值可以是普通类型、引用或const。consteval函数的返回值不允许是auto推导为引用类型,必须是可复制或移动的类型。
-
递归
constexpr函数可以递归,但编译器对递归深度有限制。consteval函数也可以递归,但编译器在编译期会进行完整展开,深度限制更严格。
-
异常处理
constexpr函数可以抛异常,只要在编译期调用时不会抛出。consteval函数不能抛异常,因为所有调用都必须在编译期完成,且编译器不支持在编译期抛异常。
-
可见性
constexpr可以在运行时使用;consteval只能在编译期调用。- 任何试图在运行时调用
consteval函数都会导致编译错误。
4. 代码示例
#include <iostream>
#include <array>
#include <concepts>
/* 1. constexpr 示例:编译期数组大小 */
constexpr std::size_t fibonacci_n(std::size_t n) {
return (n < 2) ? n : fibonacci_n(n - 1) + fibonacci_n(n - 2);
}
constexpr std::size_t N = fibonacci_n(10);
std::array<int, N> fibs; // 编译期确定大小
/* 2. consteval 示例:强制编译期检查 */
consteval int square_root(int x) {
if (x < 0) throw "负数"; // 编译期抛异常,导致编译错误
return static_cast <int>(std::sqrt(x));
}
int main() {
// constexpr 可以在运行时使用
constexpr int a = fibonacci_n(5);
std::cout << "fibonacci(5) = " << a << '\n';
// consteval 必须在编译期调用
constexpr int root = square_root(25);
std::cout << "sqrt(25) = " << root << '\n';
return 0;
}
5. 最佳实践建议
| 场景 | 推荐使用 |
|---|---|
| 需要兼容 C++17 及更早编译器 | constexpr |
| 想在编译期强制验证输入合法性 | consteval |
| 需要在运行时也能调用同一函数 | constexpr |
| 想在编译期捕获错误,防止错误实例化 | consteval |
| 想利用编译器的编译期优化但不必强制 | constexpr |
6. 小结
constexpr:灵活、兼容、可在编译期或运行时求值;适用于需要在多处使用的常量或函数。consteval:严格、强制编译期求值;适用于必须在编译期验证或生成错误的情形。
在实际项目中,先考虑兼容性与使用场景,再决定使用哪种关键字。若你只需要在编译期计算常量,使用 constexpr 即可;若你想在编译期捕获错误或强制编译期求值,选择 consteval。这样既能充分利用编译期计算的优势,又能保持代码的可维护性与可读性。