在 C++20 之前,constexpr 用来标记可以在编译期求值的变量、函数和构造函数。然而,随着 consteval 的加入,C++ 在编译期计算的能力得到了进一步提升。本篇文章将对两者进行对比,并演示如何使用 consteval 编写更高效、更安全的编译期函数。
1. constexpr 的演进
- C++11:
constexpr仅能用于无参函数、返回简单类型、单行函数体。 - C++14:支持循环和条件语句,允许多行函数体。
- C++17:引入
if constexpr,实现模板元编程的分支选择。 - C++20:支持
constexpr模板、std::array的 constexpr 初始化、operator()作为 constexpr 等。
2. consteval 的诞生
consteval 是 C++20 引入的新关键字,用于声明“必须在编译期求值”的函数。与 constexpr 的主要区别在于:
| 特性 | constexpr |
consteval |
|---|---|---|
| 强制编译期求值 | 否 | 是 |
| 运行时调用 | 允许(如果能在编译期求值则编译期,否则运行时) | 禁止 |
| 报错方式 | 在调用时可能产生链接错误或运行时错误 | 直接编译错误,明确告知无法在编译期求值 |
3. 何时使用 consteval
- 确保编译期执行:当函数的返回值直接影响模板参数或数组大小时,需要保证编译期求值。
- 避免运行时开销:若逻辑简单且必须在编译期完成,使用
consteval能防止意外的运行时调用。 - 编译期错误提示:在调试复杂模板代码时,使用
consteval能让错误尽早在编译阶段显现。
4. 示例:生成斐波那契数列
#include <iostream>
consteval unsigned long long fib(unsigned n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
int main()
{
constexpr unsigned long long val = fib(10);
std::cout << "fib(10) = " << val << '\n';
return 0;
}
此处 fib 必须在编译期求值,若调用时传入运行时变量,则编译器会报错。
5. 与模板元编程结合
使用 consteval 与 std::integral_constant 可以构建更紧凑的元编程结构。例如:
template <unsigned N>
struct Factorial
{
static constexpr unsigned value = 1 * Factorial<N-1>::value;
};
template <>
struct Factorial <0>
{
static constexpr unsigned value = 1;
};
consteval unsigned get_factorial(unsigned n)
{
return Factorial <n>::value;
}
这里 get_factorial 必须在编译期求值,并且返回的值可直接用于数组大小。
6. 性能考虑
constexpr可能在编译期或运行期执行,编译器根据上下文决定。consteval强制编译期执行,确保在生成代码时就完成计算,从而避免运行时消耗。
7. 结语
constexpr 与 consteval 各有千秋。constexpr 兼顾灵活性与可维护性,适合大多数需要编译期计算的场景;consteval 则提供了更严格的保证,帮助开发者写出更高效、更安全的 C++ 代码。熟练掌握两者的区别与用法,将使你在 C++20 及以后的开发中游刃有余。