C++20 对模板元编程(Template Metaprogramming, TMP)提供了一系列新特性,使得编写静态计算逻辑变得更直观、更安全。本文将聚焦于这些新特性,并通过示例代码展示它们在实际项目中的应用场景。
一、概览:C++20 对 TMP 的强化
-
consteval- 只允许在编译期执行的函数。编译器会在调用点直接求值,任何运行时求值都会报错。
- 与
constexpr的区别在于consteval强制在编译期求值,消除了不确定性。
-
constinit- 强制对全局或静态变量在编译期进行初始化。避免了在运行时初始化的隐式成本。
-
constexpr迭代器和容器std::vector、std::array、std::string等容器现在支持在编译期构造和操作。
-
consteval递归- 递归模板函数可以改写为
consteval函数,编译期递归实现更简洁。
- 递归模板函数可以改写为
-
if consteval/if constexpr的结合- 在编译期选择不同的实现路径,而不是在模板特化层级堆叠。
二、核心示例:在编译期生成数列
下面演示如何使用 consteval 与 std::array 在编译期生成斐波那契数列。
#include <array>
#include <iostream>
constexpr std::size_t MaxN = 10;
consteval std::array<int, MaxN> make_fibonacci()
{
std::array<int, MaxN> arr{0};
arr[0] = 0;
if constexpr (MaxN > 1)
arr[1] = 1;
for (std::size_t i = 2; i < MaxN; ++i)
arr[i] = arr[i - 1] + arr[i - 2];
return arr;
}
constinit std::array<int, MaxN> fibonacci = make_fibonacci();
int main()
{
for (int n : fibonacci)
std::cout << n << ' ';
std::cout << '\n';
}
make_fibonacci必须在编译期求值,因此使用consteval。fibonacci用constinit初始化,确保在运行时不会再执行任何计算。- 程序输出:
0 1 1 2 3 5 8 13 21 34,完全由编译期完成。
三、编译期错误捕获:使用 static_assert 与 consteval
编译期错误捕获是一大优势。下面的代码演示如何在模板参数检查中强制执行合法性。
template <typename T>
consteval T square(const T &x)
{
static_assert(std::is_arithmetic_v <T>, "square requires arithmetic type");
return x * x;
}
调用 square("abc") 会在编译期抛出错误,避免运行时异常。
四、实际应用场景
-
嵌入式系统
- 通过
consteval预计算表格,避免运行时开销。 - 使用
constinit确保全局配置在启动时完成。
- 通过
-
编译期验证
- 对模板参数进行严格检查,确保库使用者的错误被尽早捕获。
-
代码生成
- 通过
constexpr函数生成字符串模板,在编译期完成文件名或 SQL 语句拼接。
- 通过
-
性能优化
- 对小范围循环使用
constexpr容器,避免运行时分配。
- 对小范围循环使用
五、总结
C++20 的 TMP 新特性为我们提供了更强的静态检查、更直观的语义和更高的性能。通过 consteval 与 constinit,编译期计算的边界被明确,错误被及时发现。掌握这些特性可以让 C++ 开发者在不牺牲可读性的前提下,编写出更安全、更高效的代码。