在 C++20 之前,模板元编程主要依赖于递归模板实例化和 std::enable_if 等机制来实现类型级别的计算。随着 constexpr 关键字在编译期的强大功能,C++20 让我们可以在更直观、更高效的方式下完成类似的任务。本文将通过一个具体的例子,展示如何使用 constexpr 和模板结合,实现一个编译期的“斐波那契”序列计算器,以及如何在运行时使用该结果。
1. 目标:编译期计算斐波那契数列
我们希望在编译期计算给定索引 N 的斐波那契数,并在运行时直接使用该结果,而不产生任何运行时开销。C++20 的 constexpr 函数能够在编译期求值,满足这一需求。
#include <iostream>
#include <array>
#include <stdexcept>
2. constexpr 斐波那契函数
首先,定义一个递归的 constexpr 函数,使用尾递归优化来避免深层递归导致的编译器限制。
constexpr unsigned long long fib(unsigned int n, unsigned long long a = 0, unsigned long long b = 1) {
return n == 0 ? a : fib(n - 1, b, a + b);
}
a表示F(n-1),b表示F(n)。- 当
n == 0时,返回F(0),即a。
3. 编译期数组预生成
如果我们需要在运行时频繁访问斐波那契数列的多个值,可以在编译期生成一个数组。下面演示如何生成前 50 个斐波那契数。
constexpr std::array<unsigned long long, 50> make_fib_array() {
std::array<unsigned long long, 50> arr{};
unsigned long long a = 0, b = 1;
for (unsigned int i = 0; i < arr.size(); ++i) {
arr[i] = a;
auto temp = a + b;
a = b;
b = temp;
}
return arr;
}
constexpr std::array<unsigned long long, 50> fib_array = make_fib_array();
4. 在运行时使用
由于 fib_array 是 constexpr,它会在编译期被初始化,运行时访问完全是常量表达式。
int main() {
constexpr unsigned int N = 20;
std::cout << "F(" << N << ") = " << fib(N) << std::endl;
std::cout << "First 10 Fibonacci numbers:" << std::endl;
for (unsigned int i = 0; i < 10; ++i) {
std::cout << "F(" << i << ") = " << fib_array[i] << std::endl;
}
}
5. 错误处理与边界检查
虽然 constexpr 函数在编译期执行,但我们仍可以通过 static_assert 或抛异常来保证输入合法性。
template<unsigned int N>
constexpr unsigned long long safe_fib() {
static_assert(N < 93, "Fibonacci number too large for 64-bit");
return fib(N);
}
6. 小结
- 编译期计算:
constexpr让我们能在编译期完成复杂的数值计算,消除运行时负担。 - 模板与
constexpr的协作:可以通过模板参数决定计算范围,实现类型级别的灵活性。 - 性能优势:预生成数组在运行时直接访问常量,CPU 缓存友好。
通过上述示例,你可以将任何需要在编译期完成的数值或类型计算迁移到 C++20 的 constexpr 环境中,提升程序的性能和安全性。祝编码愉快!