C++ 进阶编程:模板与元编程笔记
C++ 进阶编程:模板与元编程笔记
时间:2025/12/16
关键词:泛型编程、编译期计算、零成本抽象、类型推导、约束、类型萃取
核心目标:把“同一套逻辑支持多种类型”这件事交给编译器完成。
1. 模板到底在解决什么问题
如果没有模板,很多函数只能为不同类型重复写多份:
1 | int twice(int x) { return x * 2; } |
模板的作用就是把“变化的类型”抽象成参数:
1 | template <class T> |
这样带来的好处:
- 一份代码可适配多种类型
- 类型检查发生在编译期
- 通常没有运行时额外开销
- 很适合高性能场景中的“零成本抽象”
2. 函数模板
2.1 最基本写法
1 |
|
class 和 typename 在模板类型参数里通常等价:
1 | template <typename T> |
2.2 显式指定模板参数
有时可以手动指定:
1 | std::cout << twice<int>(2) << '\n'; |
但绝大多数情况下,函数模板都可以依靠参数自动推导。
2.3 模板不是“自动重载一切”
模板函数会参与重载决议,但它不是“万能匹配”。
如果你还写了一个普通函数,编译器会按重载规则选择更合适的版本:
1 |
|
这里字符串版本不是“模板自动生成”的,而是我们手动提供了一个更合适的重载。
3. 类模板
函数可以模板化,类也可以:
1 | template <class T> |
类模板常见用途:
- 容器:
std::vector<T> - 智能指针:
std::unique_ptr<T> - 泛型工具类:
std::optional<T>、std::function<T>
4. 非类型模板参数
模板参数不一定是类型,也可以是编译期常量。
4.1 经典写法
1 |
|
这里的 N 在编译期就已经确定。
4.2 用编译期布尔值控制分支
1 |
|
if constexpr 的含义是:
- 条件在编译期判断
- 不满足条件的分支会被丢弃
- 很适合模板里的静态分发
4.3 现代 C++ 的扩展
C++17 起可以写:
1 | template <auto N> |
也就是让非类型模板参数的类型由编译器推导。
5. 模板参数推导与 auto
模板学习里最容易混乱的其实是“类型怎么被推导出来”。
5.1 值传递
1 | template <class T> |
如果传入:
int,则T = intconst int,顶层const会被忽略,通常还是T = int- 引用也会退化为值
5.2 左值引用
1 | template <class T> |
此时:
- 传左值可以
const属性会被保留
例如:
1 | const int a = 1; |
5.3 万能引用 / 转发引用
1 | template <class T> |
当 T 需要推导且参数形式为 T&& 时,它是转发引用:
- 传右值时,
T推导为普通类型 - 传左值时,
T推导为左值引用
这是完美转发的基础。
5.4 auto 的推导规则和模板很像
1 | auto x = 1; // int |
可以粗略理解为:
auto很像“让编译器帮你写模板参数推导”decltype(expr)则是“精确获取表达式类型”
6. 函数对象与 lambda
模板经常和“可调用对象”配合使用。
6.1 函数对象
1 |
|
Func 可以是:
- 普通函数指针
- 仿函数对象
- lambda
6.2 lambda 本质上也是对象
1 |
|
6.3 捕获方式
1 | [&] // 按引用捕获 |
注意:
- 按引用捕获要注意生命周期
- 闭包对象会保存按值捕获的副本
6.4 泛型 lambda
C++14 起,lambda 参数可以写 auto:
1 | auto print = [](const auto& x) { |
这相当于编译器帮我们生成了一个带模板 operator() 的闭包类型。
7. if constexpr:模板时代码分支的关键工具
模板中经常需要根据类型走不同逻辑。
1 |
|
相比普通 if,if constexpr 在模板里更重要,因为它可以彻底丢弃无效分支,避免编译错误。
8. 可变参数模板
可变参数模板可以接收任意个模板参数或函数参数。
8.1 基本写法
1 | template <class... Ts> |
这里:
Ts...是模板参数包args...是函数参数包
8.2 递归展开
1 |
|
8.3 折叠表达式
C++17 起,更推荐用 fold expression:
1 |
|
这比手写递归更直观。
9. 模板特化
当某些类型需要特殊处理时,可以使用特化。
9.1 全特化
1 |
|
9.2 偏特化
类模板支持偏特化,函数模板不支持偏特化。
1 | template <class T> |
这里 T* 就是“对一类类型进行特化”。
10. 类型萃取 type traits
type traits 是模板元编程里非常实用的一组工具,用来在编译期判断、转换、组合类型信息。
最常见的几个:
std::is_same_v<A, B>std::is_integral_v<T>std::is_floating_point_v<T>std::is_pointer_v<T>std::remove_reference_t<T>std::decay_t<T>
例子:
1 |
|
10.1 一个实用例子
1 |
|
这个模式的核心是:
decltype(x)取表达式类型std::decay_t做常见退化if constexpr做编译期分支
11. tuple:把多个异构值打包
tuple 可以装不同类型的数据。
1 |
|
常见操作:
1 | auto x = std::get<0>(info); |
也可以结构化绑定:
1 | auto [id, score, name] = info; |
tuple 在模板里很重要,因为它经常和参数包、泛型封装、返回多个值一起使用。
11.1 配合 std::apply
1 |
|
12. 编译期计算与元编程
模板元编程最早常见的写法是“模板递归”。
12.1 经典例子:编译期阶乘
1 | template <int N> |
这就是典型的模板元编程:
- 编译期递归展开
- 编译期得到结果
但现代 C++ 中,很多时候更推荐 constexpr 函数,因为更自然、可读性更好。
12.2 更现代的写法:constexpr
1 | constexpr int factorial(int n) { |
经验上可以这么记:
- 需要泛型类型操作时,用模板
- 需要编译期值计算时,优先考虑
constexpr
13. SFINAE 与 Concepts
这部分属于模板进阶重点。
13.1 SFINAE 是什么
SFINAE 全称:
Substitution Failure Is Not An Error
含义是:
- 模板替换失败时,不一定报错
- 编译器会把这个候选模板从重载集合里移除
过去常用 std::enable_if 来写约束:
1 |
|
13.2 现代写法:Concepts
C++20 更推荐直接写约束:
1 |
|
或者:
1 | template <class T> |
优势:
- 代码更直观
- 编译错误信息更友好
- 语义上更接近“声明接口要求”
14. 模板与高性能的关系
模板在高性能 C++ 里不是“语法炫技”,而是重要工具。
它常被用来实现:
- 泛型容器与算法
- 编译期分发,避免运行时
if/switch - 针对不同类型或策略做静态优化
- 内联与零成本抽象
例如一个“策略模板”:
1 | template <class Policy> |
如果策略类型在编译期确定,编译器往往可以做更激进的内联和优化。
15. 常见坑
15.1 模板报错通常很长,先看“第一处真正失败的地方”
不要一上来被几百行错误吓住。
模板错误栈很深时,先找:
- 第一个用户代码位置
- 哪个类型替换失败
- 哪个约束没满足
15.2 函数模板不能偏特化
类模板可以偏特化,函数模板不能。
函数模板通常靠:
- 重载
if constexprenable_if- concepts
来做选择。
15.3 模板定义通常要放头文件
因为模板需要在使用点可见,编译器才能实例化。
这也是很多模板库几乎全写在头文件里的原因。
15.4 typename 的两个常见位置
第一种:声明模板类型参数
1 | template <typename T> |
第二种:告诉编译器“这是一个类型”
1 | template <class T> |
因为 T::value_type 在模板阶段不一定能立刻判断它是“类型”还是“静态成员”。
16. 运行期常量 vs 编译期常量
运行期常量:
- 程序运行时才确定
- 例如函数参数
int n
编译期常量:
- 编译阶段就确定
- 例如模板参数、
constexpr结果、static_assert条件
区别的意义在于:
- 编译期常量能参与模板实例化
- 编译器可以据此裁剪分支、展开逻辑、做更多优化
17. 一页总结
模板学习的主线可以概括成:
- 用模板把“类型差异”参数化
- 用推导、特化、类型萃取处理不同类型
- 用参数包和
tuple处理变长、异构数据 - 用
if constexpr、SFINAE、concepts 实现静态约束和分发 - 用
constexpr与模板配合,把部分逻辑提前到编译期
如果只记几个最高频关键词:
- 函数模板 / 类模板
- 非类型模板参数
auto/decltype/ 引用折叠if constexprtype traits- 参数包与 fold expression
- 特化
constexpr- concepts
18. 建议继续补充的相关主题
如果后面继续整理,这几个主题和本文衔接最好:
std::forward、完美转发与引用折叠std::move、移动语义和模板中的值类别CRTP、策略类、静态多态std::invoke、std::apply、通用调用封装- ranges 与 concepts 在现代泛型编程里的用法
19. 参考资料
cppreference: templates
https://en.cppreference.com/w/cpp/language/templatescppreference: type traits
https://en.cppreference.com/w/cpp/typescppreference: fold expressions
https://en.cppreference.com/w/cpp/language/foldcppreference: concepts
https://en.cppreference.com/w/cpp/concepts