**标题:C++20 中的 consteval 与 constexpr 之争:什么时候该用哪一个?**

在 C++20 之前,constexpr 关键字已经让编译器在编译期间执行函数或生成常量值。然而,随着 constexpr 能够包含更多控制流和异常处理,编译器在运行时与编译时执行之间的界限逐渐模糊。C++20 引入了 consteval,为开发者提供了一个更严格的编译期执行声明。

1. constexpr 的现状

  • 可在编译时或运行时执行:如果编译器无法在编译期间求值,constexpr 函数会在运行时调用。
  • 语义宽松:即使函数内部有非 constexpr 表达式,编译器也可能在运行时调用。
  • 兼容性好:大多数现有代码库只需将函数标记为 constexpr,不需要改动调用点。

2. consteval 的出现

  • 强制编译期执行:任何使用 consteval 的函数都必须在编译期间求值;否则编译器报错。
  • 提高安全性:避免了意外的运行时调用,特别是在模板元编程或 constexpr 计算中。
  • 性能保证:编译期求值可以消除运行时开销,尤其适用于需要在 static_assert 或模板参数中的计算。

3. 什么时候使用 consteval

场景 推荐关键字 理由
需要在模板参数、static_assert 等处保证求值 consteval 防止错误的运行时调用
需要兼容运行时逻辑,且可以在编译时求值 constexpr 允许在无法求值时回退到运行时
需要确保所有使用点都在编译期间求值 consteval 例如在 constexpr 容器、数组大小等

4. constevalconstinit

  • constinit 用于保证全局或静态变量在程序启动前已初始化为常量,但不强制在编译期间求值。它适用于需要在编译期间初始化但不一定是纯函数的场景。
  • consteval 则更像是 constexpr 的“编译期专用版”,其返回值不可能在运行时出现。

5. 示例代码

// 必须在编译期间求值,否则编译错误
consteval int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n-1);
}

// 允许运行时求值
constexpr int fib(int n) {
    return n <= 1 ? n : fib(n-1) + fib(n-2);
}

int main() {
    constexpr int f5 = factorial(5);     // OK,编译期求值
    static_assert(f5 == 120, "factorial wrong");

    int f10 = factorial(10);            // 编译错误:consteval 必须在编译期求值
    // int f10 = fib(10);              // OK,运行时求值
}

6. 小结

  • constexpr 适合大多数需要编译期求值的情况,同时保持运行时回退的灵活性。
  • consteval 则是当你需要完全保证编译期求值,防止潜在运行时错误时的理想选择。
  • 在使用 consteval 时,需要注意调用点是否也满足编译期求值的条件,否则编译器会报错。

合理选择 constexprconsteval,可以让 C++20 的编译期计算既安全又高效,避免在模板元编程中潜藏的运行时成本。

发表评论