C++20 中的 consteval 与 constexpr 的区别

在 C++20 标准中,constexprconsteval 两者都用于表达函数在编译期执行的意图,但它们在语义、适用范围和使用限制上有着细微而重要的差别。理解这两者的区别不仅能帮助我们编写更高效、更安全的代码,还能避免潜在的编译错误和运行时成本。


1. 基本定义

关键字 说明
constexpr 声明函数或变量在编译期可求值,但若无法在编译期求值,仍可在运行期执行。
consteval 声明函数必须在编译期求值;若编译器在调用该函数时无法得到编译期常量,编译错误。

2. 编译期求值的强制性

  • constexpr:函数可以在编译期或运行期执行,编译器根据上下文决定。若提供的参数为非常量表达式,则函数将在运行期执行,返回值可能是 constexpr 或普通值。

  • consteval:函数的调用 必须 在编译期完成。任何尝试在运行期调用该函数的代码都会导致编译错误。

示例

constexpr int add(int a, int b) { return a + b; }
consteval int multiply(int a, int b) { return a * b; }

int main() {
    constexpr int x = add(2, 3);        // OK,编译期求值
    int y = add(2, 3);                 // OK,运行期求值
    constexpr int z = multiply(2, 3);  // OK,编译期求值
    int w = multiply(2, 3);            // ❌ 编译错误:consteval 函数不能在运行期调用
}

3. 适用场景

场景 适合的关键字
需要在编译期进行复杂运算,但在某些情况下可以在运行期回退 constexpr
必须确保所有调用都在编译期完成,例如类型级别计算、编译期安全检查 consteval
需要在编译期生成数组大小或模板参数 constexprconsteval(根据是否需要强制编译期)
在编译期实现安全的整数除法、范围检查 consteval(避免运行期异常)

4. 语法和行为细节

  1. 函数返回值

    • constexpr 函数返回值可以是 constexpr 或普通类型。
    • consteval 函数返回值在调用时必为 constexpr,但返回类型本身不必加 constexpr
  2. 递归调用

    • 两者都支持递归,但 consteval 的递归深度受编译期求值的限制,过深会导致编译器报错。
    • constexpr 的递归深度同样受限制,但在运行期递归可以不受此限制。
  3. 异常

    • constexpr 函数在编译期抛异常会导致编译错误;若在运行期抛异常,则按运行时处理。
    • consteval 函数在编译期抛异常必导致编译错误;运行期调用已被禁止,因而不会抛异常。
  4. 可变状态

    • 两者均要求函数不依赖任何外部可变状态。
    • consteval 对状态要求更严格,任何依赖于运行时数据的操作都会导致编译错误。

5. 性能与优化

  • consteval 由于强制编译期求值,编译器可以在编译阶段完成所有计算,生成的机器码不包含任何函数调用,直接把结果嵌入。
  • constexpr 在编译期求值时可以与 consteval 同样高效,但若在运行期执行,则会产生运行时开销。
  • 通过 if constexpr 结合 consteval 可以在编译期做分支选择,进一步减少不必要的代码路径。

6. 实战案例

6.1 编译期生成序列

// 生成 0..N-1 的整数序列,N 必须在编译期
consteval std::array<int, N> make_sequence() {
    std::array<int, N> arr{};
    for (int i = 0; i < N; ++i) arr[i] = i;
    return arr;
}

constexpr auto seq = make_sequence();  // 必须在编译期求值

6.2 编译期安全的除法

consteval int safe_div(int a, int b) {
    if (b == 0) throw "division by zero";
    return a / b;
}

constexpr int result = safe_div(10, 2); // OK
// constexpr int bad = safe_div(10, 0); // 编译错误

7. 小结

特性 constexpr consteval
编译期求值 可选 必须
运行期调用 允许 禁止
适用范围 广泛 需要强制编译期
异常处理 编译期/运行期 编译期

在现代 C++ 开发中,consteval 为我们提供了更强的编译期保证,尤其适用于类型安全、资源限制和性能关键的场景。掌握其语义与使用方式,能够让代码在安全性、可维护性和性能上获得显著提升。

发表评论