在C++11 之后,constexpr 成为了在编译期执行代码的强大工具。随着 C++20 和 C++23 的到来,constexpr 的能力愈发强大,几乎可以替代传统的 consteval、constexpr if 和 constinit 等关键字,极大地简化了编译期编程。本文将梳理 constexpr 的演变路径,并给出实用示例,帮助你在日常开发中充分利用编译期计算的优势。
1. 何为 constexpr?
- 定义:
constexpr用来声明一个对象、函数或构造函数,其值/行为可以在编译期求值。 - 核心语义:若所有输入都是常量表达式,则函数的返回值也是常量表达式。
2. 关键字演进
| 版本 | 关键字 | 主要改变 |
|---|---|---|
| C++11 | constexpr |
只能在函数体内有单条返回语句;变量必须初始化为常量表达式。 |
| C++14 | constexpr |
允许多条语句、循环、递归。 |
| C++17 | constexpr |
支持 if constexpr,可在编译期做分支。 |
| C++20 | consteval |
强制在编译期求值的函数。constexpr 允许在运行时调用,返回值在运行时仍可用。constinit 强制在编译期初始化的全局/静态变量。 |
| C++23 | constexpr |
支持 lambda、虚函数、析构函数。 更严格的 constexpr 规则,允许更复杂的数据结构。 |
3. 经典案例对比
3.1 斐波那契数列
// C++11
constexpr int fib11(int n) {
return n <= 1 ? n : fib11(n-1) + fib11(n-2);
}
// C++14
constexpr int fib14(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int tmp = a + b;
a = b;
b = tmp;
}
return b;
}
3.2 类型安全的字符串拼接
// C++20
template <typename CharT, std::size_t N1, std::size_t N2>
constexpr std::basic_string<CharT, std::char_traits<CharT>, std::allocator<CharT>>
concat(const CharT (&s1)[N1], const CharT (&s2)[N2]) {
std::basic_string<CharT, std::char_traits<CharT>, std::allocator<CharT>> res;
res.reserve(N1 + N2 - 1); // 减 1 由于各自包含空字符
for (std::size_t i = 0; i < N1 - 1; ++i) res += s1[i];
for (std::size_t i = 0; i < N2; ++i) res += s2[i];
return res;
}
注意:返回值
std::string的构造会在编译期完成,使用时无需运行时分配。
3.3 运行时与编译时混合
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
int main() {
constexpr int val = factorial(5); // 120,在编译期求值
int arr[val]; // 编译时大小
std::cout << val << '\n';
}
4. 常见陷阱与最佳实践
- 递归深度:编译器对 constexpr 递归深度有限制(默认 1000 次),可通过
-fconstexpr-depth调整。 - 异常处理:在 constexpr 函数中不允许抛出异常,除非在 C++23 之后允许
constexpr try-catch。 - 全局变量:使用
constinit代替constexpr初始化全局常量,防止被意外修改。 - 虚函数:在 C++23 中
constexpr虚函数已被允许,但仅在constexpr对象上调用。
5. 进阶:编译期 JSON 解析
#include <string_view>
#include <array>
#include <cstddef>
template<std::size_t N>
constexpr std::array<char, N> parse_json_value(std::string_view json) {
std::array<char, N> arr{};
std::size_t idx = 0;
for (char c : json) {
if (c == '"' || c == '{' || c == '}' || c == '[' || c == ']')
continue; // 忽略标点
if (idx < N) arr[idx++] = c;
}
return arr;
}
constexpr auto val = parse_json_value <10>("{\"key\":\"value\"}");
// val = {'k','e','y','v','a','l','u','e',' ','\0'}
该示例在编译期解析 JSON 字符串的一部分,适用于生成固定配置。
6. 结语
constexpr 的演进使 C++ 在编译期计算方面更加成熟。合理利用 constexpr、consteval 和 constinit,可以显著提升程序性能、减少运行时错误,并实现更为表达式优雅的代码。随着标准的进一步完善,编译期编程将成为 C++ 开发者的常规工具之一。祝你在未来的项目中玩转编译期计算,写出既安全又高效的代码!