在C++17及以后的标准中,constexpr函数的功能得到了极大的扩展,使得我们可以在编译期完成更复杂的计算。本文将从基本语法讲起,逐步深入到更高级的技巧,并给出完整的代码示例。
1. 什么是constexpr函数?
constexpr关键字标记的函数表示它可以在编译期被求值。只要调用的参数是常量表达式,函数体就会在编译时执行,结果被内联到最终的二进制文件中。
2. 语法要点
- 返回类型:必须是非引用类型,除非在C++20中引入了
constexpr auto的支持。 - 函数体:从C++11开始,
constexpr函数可以包含多条语句、if、循环等。 - 参数:参数本身可以是任何类型,只要它们在编译期可评估。
- 递归:从C++14开始,递归
constexpr函数完全可行,只要递归深度有限。
3. 一个简单示例
constexpr int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
static_assert(factorial(5) == 120, "factorial error");
这里,factorial(5)会在编译期求值为120,随后被替换到所有使用它的位置。
4. 高级技巧:模板元编程 + constexpr
将模板与constexpr结合,可以在编译期生成更加复杂的数据结构。例如,生成斐波那契数列的数组。
#include <array>
constexpr std::array<int, 10> make_fib() {
std::array<int, 10> arr{};
arr[0] = 0;
arr[1] = 1;
for (int i = 2; i < 10; ++i)
arr[i] = arr[i - 1] + arr[i - 2];
return arr;
}
constexpr auto fib = make_fib();
static_assert(fib[9] == 34, "Fibonacci error");
5. constexpr 与 std::optional
在C++20中,std::optional支持constexpr。这让我们可以在编译期安全地处理可能为空的值。
#include <optional>
#include <iostream>
constexpr std::optional <int> find_in_array(const int* arr, size_t n, int target) {
for (size_t i = 0; i < n; ++i) {
if (arr[i] == target)
return arr[i];
}
return std::nullopt;
}
constexpr std::array<int, 5> data = {1, 3, 5, 7, 9};
int main() {
constexpr auto res = find_in_array(data.data(), data.size(), 5);
static_assert(res.has_value() && res.value() == 5, "Search error");
if (res) std::cout << "Found: " << *res << '\n';
}
6. 编译期错误与诊断
如果在编译期出现错误,编译器会给出相应的错误信息。例如,递归深度过大或使用了不支持的操作会导致编译错误。
constexpr int bad = factorial(-1); // 运行时错误 -> 编译时错误
7. 性能收益
- 减小运行时负担:编译期求值消除了运行时计算。
- 更小的二进制文件:已求值的结果被内联,消除了函数调用开销。
- 更安全的代码:
static_assert可确保在编译期就发现逻辑错误。
8. 结语
constexpr函数已成为现代C++不可或缺的工具。通过合理运用,可让代码更高效、更安全,也更易于维护。希望本文能帮助你在项目中熟练使用constexpr,把计算搬到编译期,解放运行时资源。