C++ 专家(20):编译期计算——模板元编程、类型列表、编译期函数、编译期反射
更新时间:2026-09-01。本文是
languages/cpp/expert/专家层第 20 篇,接 constexpr 一切。C++ 模板元编程(TMP)是图灵完备的编译期计算系统。虽然 C++20 的 constexpr 已经能替代大部分 TMP,但理解模板元编程对理解现代 C++ 库(如 ranges、type_traits)至关重要。
本文要回答的问题
- 模板元编程是什么?为什么在编译期计算?
- 类型列表(type list)是什么?怎么遍历和操作类型?
- SFINAE 和 enable_if 的原理是什么?
- C++11/14/17/20 的编译期编程进化路线是什么?
一、模板元编程基础:编译期计算
cpp
// 编译期阶乘:用模板特化
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
static_assert(Factorial<5>::value == 120);
static_assert(Factorial<10>::value == 3628800);二、类型列表
cpp
// 类型列表:在编译期操作类型
template<typename... Ts>
struct TypeList {};
// 获取类型列表长度
template<typename>
struct Length;
template<typename... Ts>
struct Length<TypeList<Ts...>> {
static constexpr size_t value = sizeof...(Ts);
};
// 取第一个类型
template<typename>
struct Front;
template<typename T, typename... Ts>
struct Front<TypeList<T, Ts...>> {
using type = T;
};
// 使用
using MyTypes = TypeList<int, double, std::string>;
static_assert(Length<MyTypes>::value == 3);
using First = Front<MyTypes>::type; // int三、SFINAE 和 enable_if
cpp
// SFINAE:Substitution Failure Is Not An Error
// 模板实例化时,如果某个候选项失败,不会报错,而是继续尝试
// enable_if:条件满足时启用
template<typename T>
typename std::enable_if<std::is_integral_v<T>>::type
process(T value) {
std::cout << "整数:" << value << "\n";
}
template<typename T>
typename std::enable_if<std::is_floating_point_v<T>>::type
process(T value) {
std::cout << "浮点数:" << value << "\n";
}
// C++17 简化:if constexpr
template<typename T>
void process_v2(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "整数:" << value << "\n";
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "浮点数:" << value << "\n";
} else {
std::cout << "其他类型\n";
}
}四、编译期类型信息
cpp
// 检查类型是否在类型列表中
template<typename T, typename List>
struct Contains;
template<typename T, typename... Ts>
struct Contains<T, TypeList<Ts...>> {
static constexpr bool value = (std::is_same_v<T, Ts> || ...);
};
static_assert(Contains<int, MyTypes>::value);
static_assert(!Contains<char, MyTypes>::value);
// 编译期类型索引
template<typename T, typename List>
struct IndexOf;
template<typename T, typename... Ts>
struct IndexOf<T, TypeList<T, Ts...>> {
static constexpr size_t value = 0;
};
template<typename T, typename U, typename... Ts>
struct IndexOf<T, TypeList<U, Ts...>> {
static constexpr size_t value = 1 + IndexOf<T, TypeList<Ts...>>::value;
};五、C++ 编译期编程进化
| 标准 | 能力 | 工具 |
|---|---|---|
| C++98 | 模板元编程 | 模板特化、SFINAE |
| C++11 | constexpr 函数 | type_traits、enable_if、decltype |
| C++14 | constexpr 放宽 | 变量模板、constexpr 非 const 成员 |
| C++17 | if constexpr | 折叠表达式、constexpr lambda |
| C++20 | constexpr 容器 | consteval、consteval、约束模板 |
| C++23 | 编译期反射 | static reflection(提案中) |
经验: 能用 constexpr 解决的问题,不要用模板元编程。模板元编程编译慢、错误信息难懂。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 模板递归深度超限 | 编译错误 | 默认 1024 层,减少递归或增大限制 |
| SFINAE 错误信息难懂 | 大段编译错误 | 用 static_assert 提前检查 |
| 模板元编程编译慢 | 编译时间太长 | 用 constexpr 替代 |
| 模板代码膨胀 | 二进制体积大 | 提取公共逻辑到非模板函数 |
相关与延伸
下一篇:类型擦除——any、function、dyno、vtable 原理;C++ constexpr 计算,见 constexpr 一切。
一句话总结
C++ 编译期计算:模板元编程用模板特化实现编译期计算(Factorial<N>);类型列表操作类型(Length、Front、Contains);SFINAE 使模板实例化失败时继续尝试其他候选项,C++17 的 if constexpr 是更好的替代方案;C++ 编译期编程从 C++98 的模板元编程进化到 C++20 的 constexpr 容器,能用 constexpr 就不要用模板元编程。