1.模板参数的类型
模板参数分为三大类:
| 类别 | 语法 | 说明 |
|---|---|---|
| 类型参数 | typename T / class T |
代表一个类型 |
| 非类型参数 | int N, auto V (C++17) |
编译期常量值,可为整型、枚举、指针、引用、成员指针等 |
| 模板模板参数 | template<class> class Container |
参数本身是一个类模板 |
c
// 类型参数 + 非类型参数 + 模板模板参数
template<
typename T, // 类型参数
size_t N, // 非类型参数
template<typename> class Alloc // 模板模板参数
>
class Array {
T data[N];
Alloc<T> allocator;
};
2.函数模板
2.1.基本形式
c
template<typename T>
T add(T a, T b) {
return a + b;
}
// 编译器自动推导:add(1, 2) -> T=int
// 显式指定:add<double>(1, 2)
2.2.函数模板重载
函数模板之间、模板与非模板之间可以重载,匹配优先级为:
- 普通非模板函数(精确匹配)
- 特化版本(如果存在)
- 更"特化"的模板(部分排序规则)
- 更泛化的模板
c
// 通用版本
template<typename T>
void foo(T x) { cout << "generic\n"; }
// 指针版本(更特化)
template<typename T>
void foo(T* x) { cout << "pointer\n"; }
// 非模板函数
void foo(int x) { cout << "non-template int\n"; }
int a = 1;
foo(a); // 调用非模板函数
foo(&a); // 调用指针版本
foo(3.14); // 调用通用版本
2.3.函数模板的默认参数
c
template<typename T = int, typename U = double>
void print(T a, U b) {
cout << a << ", " << b << endl;
}
print<>(1, 1); // T=int, U=double
print(1, 2); // T=int, U=double
print(1, "hello"); // T=int, U=const char*
注意:函数模板不支持偏特化,只能用重载来实现类似效果。
3.类模板
3.1.基本形式
c
template<typename T, size_t N = 10>
class Stack {
T data[N];
size_t top = 0;
public:
void push(const T& val) { data[top++] = val; }
T& pop() { return data[--top]; }
};
3.2.全特化(Full Specialization)
对特定类型组合提供完全独立的实现。
c
template<typename T, typename U>
class Pair {
T first;
U second;
};
// 全特化:当 T=void, U=void 时
template<>
class Pair<void, void> {
// 完全不同的实现
};
3.3.偏特化(Partial Specialization)
对模板参数的部分约束提供特化,是类模板独有特性。
c
template<typename T, typename U>
class MyContainer { /* 通用实现 */ };
// 偏特化1:第二个参数固定为 int
template<typename T>
class MyContainer<T, int> { /* ... */ };
// 偏特化2:两个参数相同
template<typename T>
class MyContainer<T, T> { /* ... */ };
// 偏特化3:T 是指针类型
template<typename T, typename U>
class MyContainer<T*, U> { /* ... */ };
// 偏特化4:T 是引用类型
template<typename T, typename U>
class MyContainer<T&, U> { /* ... */ };
偏特化匹配规则:编译器选择最特化(most specialized)的版本。例如:
c
MyContainer<int*, int> obj1; // 匹配 T*, U(指针偏特化)
MyContainer<int, int> obj2; // 匹配 T, T(相同类型偏特化)
MyContainer<int, double>obj3; // 匹配通用版本
4.SFINAE 与约束(C++11/17/20)
在模板元编程中,SFINAE(Substitution Failure Is Not An Error)用于控制模板参与重载决议。
SFINAE(Substitution Failure Is Not An Error)是 C++ 模板系统的核心规则:
当编译器尝试用具体类型替换模板参数时,如果推导失败,不会产生编译错误,而是将该模板从重载候选集中移除。
c
// C++11/14: 使用 enable_if
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T x) { return x; }
// C++20: 使用 concept(更简洁)
template<typename T>
concept Integral = std::is_integral_v<T>;
template<Integral T>
T foo(T x) { return x; }
4.1.最简示例:理解"推导失败不是错误"
c
// 通用版本:接受任意类型
template<typename T>
void foo(T x) {
cout << "generic: " << x << endl;
}
// 指针版本:T 必须能解引用出 int 成员
template<typename T>
void foo(T x, typename T::iterator* = nullptr) {
cout << "has iterator\n";
}
foo(42); // 调用通用版本
// 尝试匹配指针版本时:
// T=int, int::iterator 不存在 → 推导失败
// 但这不是编译错误!只是移除此候选
4.2.SFINAE 发生的时机
c
模板定义 → 类型替换(Substitution) → 推导失败?
↓ ↓ ↓
编译期 编译期(重载决议) 是 → 静默丢弃
否 → 继续编译
关键点:SFINAE 只在替换阶段生效,函数体内部的错误不会被 SFINAE 捕获。
4.3.enable_if:SFINAE 的"开关"
std::enable_if 是 <type_traits> 中的工具,本质是条件类型开关。
4.3.1.原理剖析
c
// enable_if 的实现(简化版)
template<bool B, typename T = void>
struct enable_if {};
// 偏特化:当 B=true 时生效
template<typename T>
struct enable_if<true, T> { using type = T; };
// 使用
enable_if<true, int>::type // OK, type = int
enable_if<false, int>::type // 错误!没有 type 成员
4.3.2.两种使用位置
- 位置一:返回值(最常用)
c
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T x) { return x; }
// 当 T=double 时:
// enable_if<false, double>::type → 不存在
// 推导失败 → SFINAE → 此模板被移除
- 位置二:模板参数列表
c
template<
typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type
>
T foo(T x) { return x; }
// 优点:不占用返回值位置,更通用
// 缺点:默认模板参数不参与函数签名匹配
- 位置三:函数参数列表(C++11 常用技巧)
c
template<typename T>
T foo(T x,
typename std::enable_if<std::is_integral<T>::value, int>::type = 0
) { return x; }
4.3.3.多条件组合
c
// 条件:T 是整数 且 有符号
template<typename T>
typename std::enable_if<
std::is_integral<T>::value && std::is_signed<T>::value,
T
>::type
process(T x) { return -x; }
// C++14 起可用 _v 简写
template<typename T>
std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, T>
process(T x) { return -x; }
4.4.SFINAE 实战:类型特征检测
4.4.1.检测类型是否有某个成员
5.STL 中模板的常见使用模式
5.1.容器(Containers)
c
std::vector<int> v; // 类型参数
std::array<int, 100> a; // 类型 + 非类型参数
std::map<std::string, int> m; // 多类型参数
std::vector<int, MyAlloc<int>> v2; // 模板模板参数(分配器)
5.2.迭代器(Iterators)
迭代器是模板参数化的指针抽象,算法通过模板参数接受任意迭代器类型:
c
template<typename InputIt, typename T>
InputIt find(InputIt first, InputIt last, const T& value) {
for (; first != last; ++first)
if (*first == value) return first;
return last;
}
5.3.算法(Algorithms)
STL 算法大量使用函数模板,典型模式:
c
// 1. 接受迭代器范围
std::sort(v.begin(), v.end());
// 2. 接受自定义比较器(函数对象/仿函数)
std::sort(v.begin(), v.end(), std::greater<int>());
// 3. 接受一元/二元操作
std::transform(in.begin(), in.end(), out.begin(),
[](int x) { return x * 2; });
5.4.适配器(Adapters)
通过模板组合和默认参数实现功能扩展:
c
// stack 默认底层容器是 deque
template<typename T, typename Container = std::deque<T>>
class stack {
Container c;
public:
void push(const T& x) { c.push_back(x); }
// ...
};
std::stack<int> s1; // 底层为 deque
std::stack<int, vector<int>> s2; // 底层为 vector
5.5.仿函数 / 函数对象(Functors)
重载 operator() 的类模板,用于向算法传递策略:
c
template<typename T>
struct Plus {
T operator()(const T& a, const T& b) const {
return a + b;
}
};
std::plus<int> adder;
int sum = adder(3, 4); // 7
5.6.策略模式(Policy-Based Design)
通过模板参数注入行为策略,典型如 std::shared_ptr 的删除器:
c
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
T* ptr;
Deleter del;
public:
~unique_ptr() { del(ptr); }
};
5.7.类型萃取(Type Traits)
编译期类型信息查询与转换,是模板元编程的基础:
cpp
// 查询
static_assert(std::is_pointer_v<int*>); // true
static_assert(std::is_same_v<int, int32_t>); // true (通常)
// 转换
using T = std::remove_reference_t<int&>; // T = int
using U = std::add_pointer_t<int>; // U = int*
6.总结对比表
| 特性 | 函数模板 | 类模板 |
|---|---|---|
| 自动类型推导 | ✅ 支持 | ❌ 需显式指定(除 CTAD C++17) |
| 全特化 | ✅ 支持 | ✅ 支持 |
| 偏特化 | ❌ 不支持(用重载替代) | ✅ 支持 |
| 默认模板参数 | ✅ 支持(C++11 起尾部可省) | ✅ 支持 |
| 非类型模板参数 | ✅ 支持 | ✅ 支持 |
| 模板模板参数 | ✅ 支持 | ✅ 支持 |