模板(template)是为了支持泛型编程(Generic programming)而存在的,所谓泛型,也就是不依赖于具体类型,wiki对其定义如下
Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.
为了更直观的了解,我们先看看相对于一般的编程方式,范型编程是怎么样的
1 |
|
对于多种数据类型,普通函数要声明不同的版本
1 | float max(float, float); |
这将导致大量的重复工作,为此,我们需要解放自己,这时候泛型编程登场了
1 | template<typename T> |
就一个函数的事,多么简洁而优雅!模板就如蓝图一样,对于实例化模板参数的每一种类型,都从模板中产生一个不同的实体,下面就是测试
1 |
|
然后我们就会得到两个实例…
1 | //自动生成的 |
如果还不相信的话,我们就来看看对应的汇编代码
g++ -g -c source.cpp & objdump -S source.o > source_obj.s
1 | 1.o: file format Mach-O 64-bit x86-64 |
我们可以看到max一共有两个函数名
- __Z3maxIiET_S0_S0 =
int max(int, int)
- __Z3maxIdET_S0_S0,=
double max(double, double)
好嘞,对底层的验证点到为止,那么接下来我们将迎来重头戏
模板参数的推导(deduction)
函数模版有两种类型的参数
- template
: T是模板参数 - max(T x, T y) : x, y是调用参数
模板参数要求必须要全部推导出来,函数模板可以通过传入实参来推导模板参数,如果推导失败,那么就会发生错误
1 | max(1, 1.5); //max(int, double), 推导失败 |
如果没有全部推导,也会发生错误
1 | template<typename T1, typename T2> |
一般而言,不会去转化实参类型以匹配已有的实例,不过有些情况除外
const转换:
- 如果模板形参为const引用,则其可以接受const或非const引用
- 如果模板形参为const指针,则其可以接受const或非const指针
- 如果模板形参不是引用或指针(值传递),则形参和实参都忽略const
数组或函数到指针的转换
- 如果模板形参不是引用或指针(值传递),则数组会转化为指针,数组实参将当作指向其第一个元素的指针;
- 如果模板形参不是引用或指针(值传递),则函数会转化为指针,函数实参将当作指向函数类型的指针;
1 | template<typename T> |
但是有时候,模板推断可能会出乎我们的预料1
2
3
4
5
6
7
8
9template<typename T>
void fref(T& x) {
}
int main() {
const int b = 4;
fref(b);//ok!
return 0;
}
道理上来说,这样的推断是错误的,但是我们可以看到在函数中并没有对x进行任何修改,因此可以匹配
但是如果在函数中加入x=3
那么,编译就会报错,这就在情理之中了
另外从C++11开始,已经可以用模板来推导模板参数了
例如
1 |
|
函数模板的重载与匹配
1 |
|
注意,实例化模版也是有代价的,如果能通同时匹配到一般函数和模板函数,就使用一般函数
模板不允许隐式转换,因此如果两个类型不同,就会考虑一般函数(如上),max(int, float)
就会匹配一般函数