constexpr 与 consteval:C++20 里常量表达式的新标准

在 C++20 之前,constexpr 是我们实现编译期计算的唯一工具。它既可以用来标记变量,也可以用来标记函数,从而告诉编译器尝试在编译时求值。随着 consteval 的出现,C++ 进一步细化了编译期与运行期的界限。本文将从两者的语义、使用场景、实现细节以及典型误区四个方面进行系统阐述,并给出实战代码,帮助读者快速掌握两者的区别与正确使用方法。

1. 基本语义

关键词 作用 关键点
constexpr 说明对象/函数可在编译期求值,但不强制 若编译器无法在编译期完成,退回到运行时
consteval 强制在编译期求值 若无法在编译期完成,编译错误

constexpr 让函数可以在两种环境下被调用:编译期(如模板参数)和运行期。consteval 则完全排除了运行时执行的可能,任何调用都必须在编译期完成。

2. 使用场景

场景 推荐关键词
需要在编译期做数学运算,且函数会在运行时被调用 constexpr
需要确保某个函数只能在编译期使用,防止误用 consteval
需要在运行时进行复杂运算,且编译期不适用 纯普通函数

案例 1:常量表达式求斐波那契

constexpr unsigned long long fib(unsigned n) {
    return (n < 2) ? n : fib(n-1) + fib(n-2);
}
constexpr unsigned long long fib5 = fib(5); // 5

这里 fib 可以在编译期计算 fib5,但如果我们在运行时调用 fib(30),编译器会把它当作运行时函数处理。

案例 2:强制编译期求值的 consteval

consteval int square(int x) {
    return x * x;
}
int main() {
    constexpr int val = square(7);   // OK,编译期
    int a = square(10);              // ❌ 编译错误,必须在编译期
}

若你想让编译器在任何地方使用 square 时都强制执行编译期求值,consteval 是最佳选择。

3. 实现细节与约束

  1. 递归深度
    consteval 函数在编译期递归调用时,递归深度仍受编译器限制(通常为 512 或更少)。若递归深度过大,编译器会报“递归深度超限”。

  2. 异常
    consteval 函数不能抛异常。所有 throw 语句都会导致编译错误。constexpr 函数可以抛异常,但在编译期调用时也会报错。

  3. 副作用
    两者都不允许修改全局状态。consteval 在这一点上更严格,任何修改都被视为非法。

  4. 函数体大小
    constexpr 允许函数体较大,只要满足编译期可执行。consteval 在实际实现中对函数体大小并无额外限制,但编译器实现可能会因为资源限制产生警告。

  5. 返回类型
    两者都要求返回类型是可以在编译期构造的(如 intstd::arraystd::string_view 等)。constexpr 允许返回非字面类型,只要它在编译期构造即可。

4. 常见误区

误区 正确理解
constexpr 就是 consteval 不是,constexpr 允许运行时调用,consteval 必须在编译期
consteval 函数可以有副作用 错误,副作用导致编译错误
consteval 适合所有编译期计算 仅当你需要强制编译期时才用,过度使用会导致编译期计算复杂度高
constexpr 的变量一定是编译期求值 不是,如果初始化表达式在编译期不可行,则会退回到运行时

5. 综合实例:编译期数组生成

#include <array>

template<std::size_t N>
consteval std::array<int, N> make_array() {
    std::array<int, N> arr{};
    for (std::size_t i = 0; i < N; ++i) {
        arr[i] = static_cast <int>(i * i);
    }
    return arr;
}

int main() {
    constexpr auto arr = make_array <10>(); // 完全在编译期生成
    static_assert(arr[3] == 9, "元素错误");
}

这里 make_array 必须在编译期返回完整数组,若在编译期无法完成(如 N 非常大),编译器会报错。

6. 结语

constexprconsteval 分别提供了编译期与强制编译期两种工具。正确理解它们的语义差别、适用场景和限制,可以帮助我们编写更安全、更高效的 C++ 代码。尤其是在性能敏感或可配置性极高的系统中,合理利用这两者能显著提升运行时性能并减少错误。希望本文能成为你在 C++20 及以后版本中掌握常量表达式的实战指南。

发表评论