# 模板与模板元编程

> [!info] 学习导航
> 返回：[[C++ 学习指南]]
> 前置知识：[[计算机系/C++/01 C++ 概览、基础类型与函数|C++ 概览、基础类型与函数]]

模板是泛型编程的基础，泛型编程即以一种独立于任何特定类型的方式编写代码。


模板是创建泛型类或函数的蓝图或公式。库容器，比如迭代器和算法，都是泛型编程的例子，它们都使用了模板的概念。


每个容器都有一个单一的定义，比如 向量，我们可以定义许多不同类型的向量，比如 vector \<int> 或 vector \<string>。


您可以使用模板来定义函数和类，接下来让我们一起来看看如何使用。


## 函数模板

模板函数定义的一般形式如下所示：


```cpp
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
template <typename T>
T add(T a, T b) {
    return a + b;
}
// 调用示例
add(3, 5); // 编译器推导 T = int
add(2.5, 3.7); // 编译器推导 T = double
add('A', 'B'); // 编译器推导 T = char
```

在这里，type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。


下述两种在表达式中使用函数模板的方式都可以让编译器生成模板含函数并调用该模板函数


**方式一：隐式类型推导**


函数模板名(实参表)

**方式二：显式类型指定**


函数模板名<类型1, 类型2, ...>(实参表)


### 函数模板可以重载

只要它们的形参表不同即可。例如，下面两个模板可以同时存在：

```cpp
template<class T1, class T2>
void print(T1 arg1, T2 arg2)
{
 cout<<arg1<<" "<<arg2<<endl;
}
template<class T>
void print(T arg1, T arg2)
{
 cout<< arg1<< " "<< arg2<< endl;
}
```

### 函数模板规则

即使函数模板的实现中没有用到模板参数，只要它的声明是 template \<typename T>，它就仍然是一个模板，而不是普通函数。

示例：

```cpp
// 这是一个函数模板（即使没有用到 T）
template <typename T>
void foo() {
  std::cout << "This is a function template.\n";
}
```

调用 foo() 时，必须提供模板参数（如 foo\<int>()），否则会编译错误（除非模板参数可推导）。


不会自动退化为普通函数，因为 C++ 不允许隐式省略模板参数。

### 函数模板特化和部分特化机制

```cpp
template <>
void swap<int>(int& a, int& b) {
// 针对 int 类型的 swap 实现
}
```

- 这是一个 **全特化（Explicit Specialization）** 的 **swap** 函数模板，仅适用于 **int** 类型。
- 当调用 **swap\<int>(x, y)** 时，编译器会优先使用这个特化版本，而不是通用的 **swap** 模板。


**3. 为什么需要模板特化？**

- **性能优化**：针对某些类型（如 **int**），可以手动实现更高效的版本（比如直接用 **std::swap** 的默认实现）。
- **特殊处理**：某些类型可能需要特殊的交换逻辑（例如含有资源句柄的类）。
- **避免通用模板的缺陷**：通用模板可能不适用于某些特定类型。


**4. 对比通用模板**

假设有一个通用的 **swap** 模板：


```cpp
template <typename T>

void swap(T& a, T& b) {

  T tmp = a;

  a = b;

  b = tmp;

}
```


现代 C++ 推荐使用 **函数重载** 而非模板特化：

```cpp
// 通用模板
template <typename T>
const T& Max(const T& a, const T& b) { return a > b ? a : b; }
// 重载版本：针对 const char
const char* Max(const char* a, const char* b) {
  return strcmp(a, b) > 0 ? a : b;
}
```

## 类模板

正如我们定义函数模板一样，我们也可以定义类模板。泛型类声明的一般形式如下所示：


```cpp
template <class type>
class class-name {
// 类成员声明
};
```

或使用 `typename`（推荐）：

```cpp
template <typename type>
class class-name {
// 类成员声明
};
```

在这里，type 是占位符类型名称，可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

```cpp
 template <typename T1, typename T2, typename T3>
class MyClass {
    T 1 member 1;
    T 2 member 2;
    T 3 member 3;
};
```


下面的实例定义了类 Stack<>，并实现了泛型方法来对元素进行入栈出栈操作：

```cpp
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
    vector<T> elems; // 使用 vector 存储元素
public:
// 入栈
    void push(T const& elem);
// 出栈
    void pop();
// 获取栈顶元素（不删除）
    T top() const;
// 检查是否为空
    bool empty() const {
        return elems.empty();
    }
};
// 注意：类外定义必须加 template 声明
// 且作用域符为 Stack<T>::
template <class T>
void Stack<T>::push(T const& elem) {
    elems.push_back(elem); // 在 vector 尾部添加
}
template <class T>
void Stack<T>::pop() {
    if (elems.empty()) {
        throw out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back(); // 删除最后一个元素
}
template <class T>
T Stack<T> ::top() const {
    if (elems.empty()) {
        throw out_of_range("Stack<>::top(): empty stack");
    }
    return elems.back(); // 返回最后一个元素的副本
}
int main()
{
  try {
    Stack<int>     intStack; // int 类型的栈
    Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
    intStack.push(7);
    cout << intStack.top() <<endl;
// 操作 string 类型的栈
    stringStack.push("hello");
    cout << stringStack.top() << std::endl;
    stringStack.pop();
    stringStack.pop();
  }
  catch (exception const& ex) {
    cerr << "Exception: " << ex.what() <<endl;
    return -1;
  }
}
```

### typename 的作用

在模板定义语法中关键字 class 与 typename 的作用完全一样。

typename 难道仅仅在模板定义中起作用吗？其实不是这样，typename 另外一个作用为：使用嵌套依赖类型(nested depended name)，如下所示：

```cpp
class MyArray
{
```

  public：

```cpp
  typedef int LengthType;
```

.....

```cpp
}
template<class T>
void MyMethod( T myarr )
{
  typedef typename T::LengthType LengthType;
  LengthType length = myarr.GetLength;
}
```

这个时候 typename 的作用就是告诉 c++ 编译器，typename 后面的字符串为一个类型名称，而不是成员函数或者成员变量，这个时候如果前面没有 typename，编译器没有任何办法知道 T::LengthType 是一个类型还是一个成员名称(静态数据成员或者静态函数)。


在类模板外定义成员函数时，模板参数列表必须与类模板声明时的参数列表**完全一致**，不能增加、减少或改变顺序。也就是说，你在类模板外定义成员函数时使用的模板参数必须与类模板声明时的参数**一一对应**。

```cpp
template <typename T>
class Stack {
public:
  void push(const T& value);
  T pop();
};
```

✅ 正确方式：

在类外定义成员函数时，模板参数必须与类模板声明一致（这里是 template \<typename T>）：

```cpp
template <typename T>
void Stack<T>::push(const T& value) {
// 实现代码
}
template <typename T>
T Stack<T>::pop() {
// 实现代码
  return T();
}
// 错误！模板参数数量不匹配（多了一个 U）
template <typename T, typename U>
void Stack<T>::push(const T& value) {
// ...
}
// 错误！模板参数名称不匹配（用了 U 而不是 T）
template <typename U>
U Stack<U>::pop() {
// ...
}
```

**为什么必须一致？**

- **模板实例化规则**：类模板的成员函数定义必须与类模板的声明匹配，否则编译器无法正确关联成员函数与类模板。
- **类型推导依赖**：成员函数的模板参数必须与类模板的参数一致，否则会导致类型推导错误。

 类模板的声明

```cpp
template <typename T, typename U>
class MyClass { ... };
```

作用：声明一个名为 MyClass 的类模板，它有两个模板参数 T 和 U。


包含内容：成员函数的声明（如 void foo(T x);），但无实现。


 类模板参数列表

```cpp
template <typename T, typename U>
```

位置：在类模板声明和成员函数定义的开头。


作用：声明模板的泛型参数（T 和 U 是类模板参数）。


成员函数定义

```cpp
template <typename T, typename U>
void MyClass<T, U>::foo(T x) { ... }
```

作用：提供成员函数的具体实现。


必须匹配：


模板参数列表（template \<typename T, typename U>）必须与类模板声明一致。


类名后的模板参数（MyClass\<T, U>）必须包含所有类模板参数（即使未使用）。

### 特殊情况：成员函数模板

如果你想在成员函数中使用**额外的模板参数**（不同于类模板的参数），可以定义**成员函数模板**，但这与类模板的成员函数定义是不同的概念：

```cpp
template <typename T>
class Stack {
public:
  template <typename U>
void foo(const U& value); // 成员函数模板
};
// 定义成员函数模板（注意：这里有两个模板参数）
template <typename T>
template <typename U>
void Stack<T>::foo(const U& value) {
// 实现代码
}
```

### 声明（Declaration）

- **作用**：告诉编译器某个实体（变量、函数、类等）的存在及其基本属性（如类型、名称），但不分配内存或提供具体实现。
- **特点**：
  - 可以多次声明（不冲突）。
  - 不生成实际代码或占用内存。
- **常见形式**：
  - 函数声明：**void foo(int x);**
  - 类声明：**class MyClass;**
  - 类模板声明：**template \<typename T> class MyVector;**

**示例：类模板的声明**

```cpp
// 类模板的声明（包含成员函数声明）
template <typename T>
class Container {
public:
void add(const T& item); // 成员函数声明
T get(int index) const; // 成员函数声明
private:
  T* data;
};
```

- 这里只声明了**Container**类和它的成员函数，但没有实现（定义）它们。

### 类模板参数（Template Parameters）

类模板的参数是指 **模板声明中定义的占位符类型或值**（如 **T**、**int N** 等），它们在实例化时被替换为具体类型或值。
 参数分为两种：

1. **类型参数**（用 **typename** 或 **class** 声明）：

```cpp
template <typename T> // T 是类型参数
```

1. **非类型参数**（用具体类型声明，如 **int**）：

```cpp
template <typename T, int N> // N 是非类型参数
```

**关键点：**

- 模板参数是 **声明的一部分**，用于定义模板的泛型行为。
- 实例化时，参数会被替换为具体类型（如 **MyClass\<int>** 中的 **T     = int**）


### 定义（Definition）

- **作用**：提供实体的完整实现或内存分配，编译器会根据定义生成代码。
- **特点**：
  - 只能定义一次（违反会导致链接错误）。
  - 对变量：分配内存。
  - 对函数/类：提供具体实现。
- **常见形式**：
  - 函数定义：**void foo(int x) { /\* 实现 \*/ }**
  - 类定义：**class MyClass { /\* 成员和数据 \*/ };**
  - 类模板成员函数定义：**template \<typename T> void Container\<T>::add(const      T& item) { /\* 实现 \*/ }**

**示例：类模板成员函数的定义**

```cpp
// 类模板成员函数的定义（必须匹配声明）
template <typename T>
void Container<T>::add(const T& item) {
// 具体实现（例如将 item 存入 data 数组）
}
template <typename T>
T Container<T>::get(int index) const {
// 具体实现（例如返回 data[index]）
}
```

- 这些定义必须与类模板的声明严格匹配（包括模板参数**T**和类名后的**Container\<T>**）。

| **特性**               | **声明（Declaration）**                   | **定义（Definition）**                               |
| ---------------------- | ----------------------------------------- | ---------------------------------------------------- |
| **目的**               | 告诉编译器“某个东西存在”                  | 告诉编译器“这个东西的具体实现”                       |
| **内存/代码生成**      | 不分配内存或生成代码                      | 分配内存或生成可执行代码                             |
| **重复性**             | 可多次声明（如头文件中的函数声明）        | 只能定义一次（否则链接错误）                         |
| **类模板中的成员函数** | 在类模板内部声明（如 **void foo(T x);**） | 在类模板外部定义（需完整模板语法）                   |
| **示例**               | **template  \<typename T> class MyClass;** | **template  \<typename T> void MyClass\<T>::foo() {}** |


**. 用 T 定义数据成员的类型**

```cpp
std::vector<T> data; // data 是一个 vector，元素类型由 T 决定
```


- 当 **Container\<int>** 实例化时，**data** 的类型是 **std::vector\<int>**。
- 当 **Container\<std::string>** 实例化时，**data** 的类型是 **std::vector\<std::string>**。

**2. 用 T 定义成员函数的参数和返回值**

```cpp
void add(const T& item); // 参数类型是 T 的引用
T get(size_t index) const; // 返回值类型是 T
```


- **add** 接受一个 **T** 类型的参数（如 **int** 或 **std::string**）。
- **get** 返回 **T** 类型的值。

**3. 用 T 定义类内部的类型成员（嵌套类型）**

```cpp
typedef T ValueType; // 定义 ValueType 为 T 的别名
using Iterator = typename std::vector<T>::iterator; // 定义 Iterator 类型
```

- **ValueType** 是 **T** 的别名，外部可通过 **Container\<int>::ValueType** 访问。
- **Iterator** 是 **std::vector\<T>::iterator** 的别名，用于迭代 **data** 成员。


此时由类模板产生

类模板名《类型》

类模板名《类型1，类型2》


### 模板元编程

## 相关内容
- [[计算机系/C++/14 STL 基础、容器与迭代器|STL 基础、容器与迭代器]]

---

## 学习导航
- 上一篇：[[计算机系/C++/11 多态与虚函数|多态与虚函数]]
- 返回目录：[[C++ 学习指南]]
- 下一篇：[[计算机系/C++/13 异常处理与智能指针|异常处理与智能指针]]
