为 int 和 double 各写一份 maximum,函数体完全一样,只是类型不同。函数模板用一份源码描述这种重复:编译器在调用处按实参类型生成具体函数。本质仍是函数,只是前面加了 template <typename T>,把类型变成参数。不是运行时多态,也不是发明新语法去写 max;生成发生在编译期。类模板同理,std::vector<int> 就是类模板 vector 填上 int。本节先写出可运行的 maximum,再给出类模板 Box,并联系函数重载与STL 容器。

若用过 Java 泛型,目标相近:一份代码用于多种类型。实现不同。Java 的 List<Integer> 在运行时类型会被擦成普通 List,不能写 List<int>,基本类型要装箱。C++ 模板在编译期为每种 T 各生成一份代码,vector<int> 里就是真正的 int;T 若不能做 >,也是编译失败,而不是等到运行。

没有模板时

int maximum(int a, int b) { return (a > b) ? a : b; }
double maximum(double a, double b) { return (a > b) ? a : b; }

这是合法的函数重载,但每多一种类型就要再抄一份。模板把类型抽成 T:

template <typename T>   // T 由调用处的实参类型填上
T maximum(const T& a, const T& b) {
    return (a > b) ? a : b;
}

函数模板

maximum(3, 7) 推导 T 为 int;maximum(2.5, 1.1) 推导为 double。T 必须能做 > 比较,否则实例化失败。

C++ 函数模板运行
#include <iostream>
#include <string>

template <typename T>
T maximum(const T& a, const T& b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << maximum(3, 7) << " "
              << maximum(2.5, 1.1) << " "
              << maximum(std::string("ab"), std::string("z")) << std::endl;
    return 0;
}

输出:

7 2.5 z

两实参类型不同时,推导可能失败。可显式写出类型:maximum<double>(1, 2.0),把 1 当成 double 来用。

类模板

类模板让同一个盒子能装不同元素类型。使用时必须写出 Box<int>,编译器不会从构造实参单独省掉类名上的 <T>(C++17 起有类模板参数推导,入门仍建议写全):

C++ 类模板 Box运行
#include <iostream>
#include <string>

template <typename T>
class Box {
    T value_;
public:
    Box(T v) : value_(v) {}
    T get() const { return value_; }
};

int main() {
    Box<int> a(42);
    Box<std::string> b(std::string("hi"));
    std::cout << a.get() << " " << b.get() << "\n";
    return 0;
}

输出:

42 hi

std::vector<T>、std::pair<T, U> 都是类模板。模板定义通常放在头文件里,因为编译器在每个使用处都要看见完整定义,见头文件与多文件。

了解:特化与编译诊断

若某个类型需要完全不同的实现,类模板可以全特化,例如单独写 Box<bool>。函数模板一般用重载代替偏特化。实例化失败时诊断往往很长,从错误信息末尾往上看第一次不满足的要求(例如缺少 operator>)更容易定位。

本节说明了函数模板与类模板的入门写法。下一节介绍STL 容器概览:vector、map、set 等。