C++: 模板元编程入门
最后更新:2026-08-26
第46课我们学了智能指针进阶。
现在,我们要接触C++的"黑魔法"——模板元编程。
模板元编程是在编译期执行代码的技术,可以大幅提升运行时性能。
1. 模板元编程概述
(1) 1.1 什么是模板元编程?
模板元编程(Template Metaprogramming,TMP)是利用模板在编译期执行计算的技术。
特点:
- 计算在编译期完成,运行时零开销
- 代码复杂,编译时间长
- 错误信息难读
(2) 1.2 为什么要用模板元编程?
| 优势 | 说明 |
|---|---|
| 性能 | 编译期计算,运行时更快 |
| 类型安全 | 编译期类型检查 |
| 泛型 | 写出真正的泛型代码 |
2. 编译期计算
(1) 2.1 编译期阶乘
示例:用模板计算阶乘(难度⭐⭐⭐)
▶ 示例 1:代码示例(难度⭐)
#include <iostream>
// 主模板
template<int N>
struct Factorial {
static const int value = N * FactorialN-1::value;
};
// 特化:终止条件
template<>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "5! = " << Factorial<5>::value << std::endl; // 120
// 编译期计算出结果,运行时直接用
return 0;
}
输出:
5! =
▶ 示例 2:代码示例(难度⭐)
💡 提示:
- 用模板特化作为递归终止条件
static const int value是编译期常量
(2) 2.2 constexpr函数(C++11,推荐)
C++11引入了 constexpr,让编译期计算更简单。
示例:用constexpr计算阶乘(难度⭐⭐)
#include <iostream>
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5); // 编译期计算
std::cout << "5! = " << result << std::endl; // 120
return 0;
}
输出:
5! =
💡 提示:
constexpr比模板元编程更简洁,优先用constexpr
3. 类型萃取
(1) 3.1 什么是类型萃取?
类型萃取(Type Traits)是在编译期查询或修改类型信息的技术。
示例:用type_traits判断类型(难度⭐⭐)
#include <iostream>
#include <type_traits>
int main() {
std::cout << std::is_integralint::value << std::endl; // 1(true)
std::cout << std::is_integraldouble::value << std::endl; // 0(false)
std::cout << std::is_pointer<int*>::value << std::endl; // 1(true)
return 0;
}
(2) 3.2 自定义类型萃取
示例:判断是否有成员函数(难度⭐⭐⭐⭐)
#include <iostream>
#include <type_traits>
// 检测是否有serialize成员函数
template<typename T>
struct has_serialize {
private:
template<typename U>
static auto test(int) -> decltype(std::declvalU().serialize(), std::true_type{});
template<typename U>
static std::false_type test(...);
public:
static const bool value = decltype(testT(0))::value;
};
struct Person {
void serialize() {}
};
int main() {
std::cout << has_serializePerson::value << std::endl; // 1(true)
std::cout << has_serializeint::value << std::endl; // 0(false)
return 0;
}
4. SFINAE
(1) 4.1 什么是SFINAE?
SFINAE(Substitution Failure Is Not An Error):模板参数替换失败不是错误,编译器会尝试其他重载。
用途: 根据类型特性选择不同的函数重载。
(2) 4.2 示例:SFINAE选择重载(难度⭐⭐⭐⭐)
#include <iostream>
#include <type_traits>
#include <string>
// 版本1:适用于整数类型
template<typename T>
typename std::enable_if<std::is_integralT::value, std::string>::type
toString(T value) {
return std::to_string(value) + " (整数)";
}
// 版本2:适用于其他类型
template<typename T>
typename std::enable_if<!std::is_integralT::value, std::string>::type
toString(T value) {
return "非整数";
}
int main() {
std::cout << toString(42) << std::endl; // 42 (整数)
std::cout << toString(3.14) << std::endl; // 非整数
return 0;
}
输出:
(程序输出)
5. 可变参数模板
(1) 5.1 什么是可变参数模板?
可变参数模板(Variadic Template)允许模板接受任意数量的参数。
示例:打印任意数量参数(难度⭐⭐⭐)
#include <iostream>
// 递归终止函数
void print() {
std::cout << std::endl;
}
// 递归打印
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归调用
}
int main() {
print(1, 2.5, "hello", 'a');
// 输出:1 2.5 hello a
return 0;
}
6. C++17折叠表达式
(1) 6.1 基本用法
C++17引入了折叠表达式(Fold Expression),简化可变参数模板。
示例:求和(难度⭐⭐)
#include <iostream>
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 折叠表达式
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
return 0;
}
▶ 示例 3:constexpr 编译期计算(难度⭐)
#include <iostream>
// constexpr 函数在编译期求值
constexpr int square(int n) {
return n * n;
}
constexpr int cube(int n) {
return n * n * n;
}
int main() {
// 编译期计算,直接嵌入结果
constexpr int sq = square(5);
constexpr int cb = cube(3);
std::cout << "5^2 = " << sq << std::endl;
std::cout << "3^3 = " << cb << std::endl;
// 静态断言验证编译期求值
static_assert(sq == 25, "square(5) should be 25");
static_assert(cb == 27, "cube(3) should be 27");
return 0;
}
输出:
5^2 =
3^3 =
❓ 常见问题
Q:模板元编程难学吗? A:难。建议先掌握基础模板,再学元编程。大多数项目不需要元编程。
constexpr,很多编译期计算可以用 constexpr 函数实现。📖 小节
| 知识点 | 要点 |
|---|---|
| 编译期计算 | 模板递归或constexpr |
| 类型萃取 | type_traits 库 |
| SFINAE | 根据类型选择重载 |
| 可变参数模板 | 接受任意数量参数 |
| 折叠表达式 | C++17,简化可变参数 |
📝 作业
-
**基础题 (Difficulty ⭐):编写一个 constexpr 函数计算阶乘,在编译期求值。在 static_assert 中验证结果。
-
**进阶题 (Difficulty ⭐⭐):用 std::enable_if 实现一个函数模板,仅当 T 是整数类型时才启用。对浮点类型应编译报错。
-
**挑战题 (Difficulty ⭐⭐⭐):用可变参数模板实现一个 print_all 函数,接受任意数量和类型的参数,逐个输出。提示:用递归展开或折叠表达式(C++17)。
- 模板元编程:编译时计算,运行时零开销
- constexpr 编译期求值
- SFINAE:替换失败不是错误
- 类型萃取(type traits)查询类型属性
- 可变参数模板处理任意数量参数
下一课:C++17/20新特性(#48)