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|编译期递归


“模板元编程 = 在编译时运行的 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 实例化对应不同类型的类定义。

📦 示例 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
  • 无运行时开销。

“模板递归 = 编译时循环。”


📦 示例 4:constexpr 阶乘

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

constexpr int val = factorial(5); // ✅ 编译期计算

🧠 区别:

方法阶段特点
模板递归编译期类型驱动,复杂但灵活
constexpr编译期或运行期语法更简洁

📦 示例 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

🧠 讲解:

  • 特化允许为特定类型定制行为。
  • 编译器根据匹配度选择最合适版本。

📦 示例 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 是 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 让模板支持条件编译期分支。”


📦 示例 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 是一组模板结构体,用于在编译期获取类型信息。

📦 示例 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_ifif constexpr 结合形成“编译期条件系统”

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 区分整型与浮点型输出行为。


特性模板元编程运行时编程
执行时机编译期程序运行时
性能零运行时开销有运行时成本
可读性复杂简单
灵活性高(泛型)
调试难度

🧠 一句话总结:

模板元编程是“编译器在帮你跑程序”。


“模板是类型的函数;

编译期递归是循环;

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 看,而是在教编译器生成代码。” 🧠