cs106l-lecture-6-3-template-metaprogramming
Course: 斯坦福大学: C++标准库编程 | C++ Standard Library Programming
🎮 CS106L Lecture 6.3 — Template Metaprogramming
🏛 Stanford CS106L — Standard C++ Programming Language
📍 主题:模板元编程(Template Metaprogramming)
🎯 目标:理解 C++ 模板在编译期的计算能力,掌握编译期逻辑与类型推导机制,认识现代 C++ 元编程的思想与用法。
🧭 课程定位
本讲是 CS106L 的收官嘉宾讲座。
讲师以“模板是 C++ 编译器的编程语言”为主题,展示了从函数模板、类模板,到「编译期计算(Compile-time Computation)」的演化。
💡 “模板不仅是泛型工具,它是一门在编译时运行的语言。”
🏷 关键词速记
Template|模板
Metaprogramming|元编程
constexpr|编译期常量
SFINAE|替代失败非错误
Partial Specialization|偏特化
Type Traits|类型萃取
Concepts|概念约束
Compile-time recursion|编译期递归
🎮 30 秒课程摘要
“模板元编程 = 在编译时运行的 C++ 程序。
它让类型、逻辑、常量在编译期完成推导与优化。”
📚 一、模板元编程是什么?
⚙️ 定义
模板元编程(TMP)是利用 C++ 模板系统在 编译时执行逻辑计算 的技术。
🧠 它能做什么:
- 在编译时执行算法(如计算阶乘)
- 推导类型关系(如判断两个类型是否相同)
- 启用 / 禁用某些函数(SFINAE)
- 实现零运行时开销的逻辑分支
📦 一句话:
“编译器在编译阶段执行 C++ 代码的一部分,生成最终机器代码。”
📦 二、函数模板回顾
📦 示例 1:函数模板
template <typename T>
T add(T a, T b) {
return a + b;
}
int x = add(2, 3); // 推导 T=int
double y = add(1.2, 3.4); // 推导 T=double
🧠 讲解:
- 模板函数在编译时被实例化为具体类型。
- 模板 ≈ “类型参数化的函数工厂”。
📚 三、类模板与类型推导
📦 示例 2:类模板
template<typename T>
class Box {
public:
T value;
explicit Box(T val) : value(val) {}
void print() const { std::cout << value << "\n"; }
};
Box<int> b1(42);
Box<std::string> b2("CS106L");🧠 说明:
- 模板类在编译时生成特定类型版本。
- 每个
T实例化对应不同类型的类定义。
📚 四、编译期计算(Compile-time Computation)
🧩 1️⃣ 递归模板计算
📦 示例 3:编译期阶乘
template<int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static const int value = 1;
};
int result = Factorial<5>::value; // ✅ 编译期已计算为 120
🧠 讲解:
- 模板递归在编译阶段展开。
Factorial<5>→ 展开为5 * 4 * 3 * 2 * 1- 无运行时开销。
“模板递归 = 编译时循环。”
⚙️ 2️⃣ constexpr 函数(C++11 起)
📦 示例 4:constexpr 阶乘
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int val = factorial(5); // ✅ 编译期计算
🧠 区别:
| 方法 | 阶段 | 特点 |
|---|---|---|
| 模板递归 | 编译期 | 类型驱动,复杂但灵活 |
| constexpr | 编译期或运行期 | 语法更简洁 |
📚 五、模板特化(Specialization)
🧩 1️⃣ 全特化
📦 示例 5:完全特化
template<typename T>
struct Printer {
static void print(const T& val) {
std::cout << val << "\n";
}
};
template<>
struct Printer<bool> {
static void print(bool val) {
std::cout << (val ? "true" : "false") << "\n";
}
};
Printer<int>::print(42); // 输出 42
Printer<bool>::print(true); // 输出 true
🧠 讲解:
- 特化允许为特定类型定制行为。
- 编译器根据匹配度选择最合适版本。
⚙️ 2️⃣ 偏特化(Partial Specialization)
📦 示例 6:偏特化
template<typename T, typename U>
struct Pair {
static void info() { std::cout << "General\n"; }
};
// 偏特化版本:当两个类型相同时
template<typename T>
struct Pair<T, T> {
static void info() { std::cout << "Same Type\n"; }
};
Pair<int, double>::info(); // General
Pair<int, int>::info(); // Same Type
🧠 讲解:
- 偏特化允许为“部分匹配类型”提供不同实现。
- 是模板系统实现多态行为的关键。
📚 六、SFINAE(Substitution Failure Is Not An Error)
⚙️ 定义
SFINAE 是 C++ 模板的核心规则:
当模板参数替换失败时,不报错,而是忽略该模板版本。
📦 示例 7:函数重载选择
template<typename T>
auto has_begin(int) -> decltype(std::declval<T>().begin(), std::true_type{});
template<typename>
auto has_begin(...) -> std::false_type;
template<typename T>
constexpr bool has_begin_v = decltype(has_begin<T>(0))::value;🧠 讲解:
- 利用
decltype检查类型是否具有成员函数。 - 若
T.begin()无法替换 → 该模板被忽略。
“SFINAE 让模板支持条件编译期分支。”
📦 七、if constexpr(C++17)
📦 示例 8:编译期条件分支
template<typename T>
void printType(const T& val) {
if constexpr (std::is_integral_v<T>)
std::cout << val << " is integral\n";
else
std::cout << val << " is not integral\n";
}🧠 讲解:
if constexpr在编译期判断条件。- 不满足分支的代码不会被编译。
- 取代了 SFINAE 的复杂写法。
📚 八、Type Traits(类型萃取)
Type Traits 是一组模板结构体,用于在编译期获取类型信息。
📦 示例 9:常见 Type Traits
std::is_integral<int>::value // true
std::is_same<int, double>::value // false
std::is_const<const int>::value // true
📦 自定义 trait
template<typename T>
struct is_string : std::false_type {};
template<>
struct is_string<std::string> : std::true_type {};🧠 用途:
- 辅助模板选择、静态断言、类型分支
- 与
enable_if、if constexpr结合形成“编译期条件系统”
📚 九、Concepts(C++20)
🧩 1️⃣ 定义与用途
Concepts 提供了更清晰的模板约束语法。
📦 示例 10:Concept 定义
#include <concepts>
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<Addable T>
T add(T a, T b) { return a + b; }📦 调用:
add(2, 3); // ✅ OK
add("a", "b"); // ❌ 编译期错误:不满足 Addable
🧠 优点:
- 可读性更强
- 错误信息更清晰
- 替代复杂的 SFINAE 模板匹配逻辑
🧠 十、模板元编程的威力
📦 示例 11:编译期最大值
template<int A, int B>
struct Max {
static const int value = (A > B) ? A : B;
};
constexpr int m = Max<10, 42>::value; // ✅ 编译期计算
📦 示例 12:编译期斐波那契
template<int N>
struct Fib {
static const int value = Fib<N-1>::value + Fib<N-2>::value;
};
template<> struct Fib<0> { static const int value = 0; };
template<> struct Fib<1> { static const int value = 1; };🧠 意义:
- 所有逻辑在编译时执行,无运行时开销。
- 为泛型算法和高性能库(如 STL、Eigen、Boost)提供基础。
🧪 十一、实战练习
✅ 练习 1:编译期阶乘
实现 Factorial<N>::value。
✅ 练习 2:类型 traits
定义 is_pointer<T>,判断类型是否为指针。
✅ 练习 3:SFINAE 检查函数
实现模板函数 has_to_string<T>,检测类型是否有 to_string() 方法。
✅ 练习 4:if constexpr
用 if constexpr 区分整型与浮点型输出行为。
🧩 十二、模板元编程 vs 运行时编程
| 特性 | 模板元编程 | 运行时编程 |
|---|---|---|
| 执行时机 | 编译期 | 程序运行时 |
| 性能 | 零运行时开销 | 有运行时成本 |
| 可读性 | 复杂 | 简单 |
| 灵活性 | 高(泛型) | 中 |
| 调试难度 | 高 | 低 |
🧠 一句话总结:
模板元编程是“编译器在帮你跑程序”。
🧠 十三、记忆口诀
“模板是类型的函数;
编译期递归是循环;
SFINAE 让模板思考;
Concepts 让模板说话。”
🪄 工程设计建议
- ✅ 优先使用
if constexpr代替 SFINAE - ✅ 使用标准 Type Traits (
<type_traits>) 而非自定义复杂判断 - ✅ 对外暴露 Concepts 而非模板技巧
- ✅ 将模板逻辑保持在头文件中(模板需可见定义)
- ⚠️ 避免模板嵌套过深,降低编译时间
- ⚙️ 模板错误调试可借助编译器 flag (
ftemplate-backtrace-limit)
✅ 十四、小结表
| 技术 | 阶段 | 功能 | 示例 |
|---|---|---|---|
| 函数模板 | 编译期 | 泛型函数 | template<typename T> T add() |
| 类模板 | 编译期 | 泛型类型 | template<typename T> class Box |
| 模板特化 | 编译期 | 针对特例优化 | template<> struct Printer<bool> |
| SFINAE | 编译期 | 条件选择 | enable_if / decltype |
| constexpr | 编译期 | 常量计算 | constexpr int factorial() |
| Concepts | 编译期 | 类型约束 | template<Addable T> |
“模板元编程让编译器成为你的合作程序员。”
📚 拓展阅读
- 📘 C++ Templates: The Complete Guide (2nd Ed.) — David Vandevoorde, Nicolai Josuttis
- 📗 Effective Modern C++ — Scott Meyers
- 📙 Modern C++ Design — Andrei Alexandrescu
- 🧠 CppReference: Template Metaprogramming
- 🪄 Boost MPL / Hana — 模板元编程库
🎯 总结一句话
“模板元编程是让 C++ 在编译期思考的艺术。
当你掌握它,你不再写代码给 CPU 看,而是在教编译器生成代码。” 🧠