在 C++20 之前,constexpr 已经为我们提供了编译期计算的能力,但在 C++20 中它被彻底提升,变成了真正可以在编译时执行几乎所有常规代码的强大工具。下面我们将从历史回顾、语义演变、典型应用、常见陷阱以及性能收益四个维度展开讨论。
1. 历史回顾
- C++11:
constexpr仅限于常量表达式;函数体只能是单个return语句,且参数必须是constexpr。 - C++14:允许在
constexpr函数中使用循环、递归、以及多语句;但仍不能包含局部非constexpr变量、异常处理等。 - C++17:进一步放宽,支持更复杂的控制流、
try-catch,并允许在类中使用constexpr成员。 - C++20:彻底解放,几乎所有标准库容器都获得了
constexpr支持;局部变量可为非constexpr,但若满足条件可在编译期求值。
2. 语义演变
| 版本 | 关键限制 | 主要改进 |
|---|---|---|
| C++11 | 只能单返回 | 支持单行返回 |
| C++14 | 只能递归 | 支持循环、递归 |
| C++17 | 不能异常 | 支持异常处理 |
| C++20 | 不能使用非 constexpr 类 |
大多数 STL 支持 constexpr |
核心概念:如果函数的所有输入都是 constexpr,且执行过程中没有违反 constexpr 的限制,编译器将尝试在编译期求值。 这意味着我们可以把任何可在编译时确定的计算逻辑写成普通函数,而不必担心手动写宏或模板元编程。
3. 典型应用
3.1 编译期矩阵乘法
#include <array>
template <std::size_t N>
using Matrix = std::array<std::array<int, N>, N>;
constexpr int multiply(int a, int b) { return a * b; }
template <std::size_t N>
constexpr Matrix <N> matmul(const Matrix<N>& A, const Matrix<N>& B) {
Matrix <N> C{};
for (std::size_t i = 0; i < N; ++i)
for (std::size_t j = 0; j < N; ++j)
for (std::size_t k = 0; k < N; ++k)
C[i][j] += multiply(A[i][k], B[k][j]);
return C;
}
在编译期生成 `Matrix
` 的乘积,得到一个全静态初始化的数据结构,避免了运行时的开销。 ### 3.2 常量表达式日志格式 “`cpp #include #include constexpr std::string_view format_log(const char* level, const char* msg) { if (level == nullptr || msg == nullptr) return “invalid”; std::string_view prefix = “[“; // placeholder // … 拼接逻辑 return “Formatted log”; // 伪代码 } “` 编译期格式化可以在模板元编程中生成错误消息,或者在 `static_assert` 中给出更友好的提示。 ### 3.3 递归 Fibonacci “`cpp constexpr unsigned long long fib(unsigned n) { return n