一、定义函数模板
1、基本范例
|
|
模板定义以关键字 template
开始,后接模板形参表,模板形参表是用尖括号<>
括住的一个或多个模板形参的列表,用逗号分隔,不能为空。
- 模板程序应该尽量减少对实参类型的要求。
- 函数模板和类模板成员函数的定义通常放在头文件中。
2、模板参数
2.1 类型参数
类型参数前必须使用关键字class
或者typename
,这两个关键字含义相同,可以互换使用。旧的程序只能使用class
。但是有些时候,class
并不合适
2.2 默认参数
|
|
调用Testfunc()函数的时候,不用指定第3个实参,因为第3个参数有默认值。要注意默认参数的写法:针对当前的范例,类型模板参数F给了默认值,函数的形参也给了默认值。默认模板参数F是一个函数指针类型(FuncType),函数参数funcpoint = mf中的mf是函数名,代表函数首地址
另外,函数模板的默认模板参数可以放在前面(这一点类模板默认模板参数不一样,类模板的模板参数一旦有一个是默认参数,则其后续的参数都需要是默认参数)。
2.3 非类型参数
除了定义类型参数,还可以在模板中定义非类型参数(nontype parameter) 表示一个值而非一个类型。 当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达,从而允许编译器在编译时实例化模板。
|
|
一个非类型参数可以是一个整形,或者是一个指向对象或函数类型的指针或引用,绑定到非类型整数参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参,必须具有静态的生存期。
但是,并不是任何类型的参数都可以作为非类型模板参数,一般有以下一些是允许的 (1)整型或枚举类型。 (2)指针类型。 (3)左值引用类型。 (4)auto或decltype(auto)。对于decltype(auto)这个用法,其中的auto理解成要推导的类型,而decltype理解成推导过程采用decltype推导。 (5)可能还有其他类型,请读者自行在学习或阅读他人代码的过程中收集和总结。
二、 实例化函数模板
这里可以给实例化一个定义:用具体的“类型”代替“类型模板参数”的过程就叫作实例化(也称为代码生成器)。
1、一个错误的实例化示例
|
|
所以,同样一个函数模板,可能以某种方式进行调用是合法的,而换一种方式调用就不合法了。尤其值得注意的是,这种合法性,在==编译阶段就可以由编译器判断出来==,因为这些对Sub()函数模板的调用代码就在这里摆着,编译器有能力在编译时就从这些调用代码中去推断Sub()函数模板中的模板参数T的类型。根据模板参数T的类型,编译器就能够判断出这个类型是否支持减法运算。
2、编译器视角的实例化
|
|
|
|
这说明在编译阶段,在对模板进行具体针对某类型的实例化之前,编译器需要查看函数模板的函数体,确定能否针对该类型进行实例化
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码
当编译器遇到类和普通函数
- 普通函数 当我们调用一个函数时,编译器只需要掌握函数的声明
- 类 当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现 我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中
为了生成个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义,所以函数模板与类模板成员函数的定义通常放在头文件中。
3、模板参数的实例化
|
|
3.1 显示实例化
|
|
3.2 隐式实例化
|
|
编译器用函数实参来为我们推断模板实参,实参类型是int。编译器会推断出模板实参为int , 并将它绑定到模板参数T。 编译器用推断出的模板参数来为我们实例化(instantiate) 一个特定版本的函数
隐式实例化下的空参数列表
|
|
< >
的作用:< >
没什么用处,但是当有一个也叫作mydouble()
的普通函数存在时,< >
也许就会发挥作用
3.3 部分实例化
|
|
通过尖括号指定一部分模板参数,另外一部分模板参数可以让编译器去推断。但是,一旦从某个模板参数开始推断,后续的所有模板参数都需要让编译器推断,==不可以自己指定第1个类型V和第3个类型U,然后推断中间第2个类型T,编译器不支持这种语法。==
3.4 特化
|
|
3.4.1 全特化
所谓全特化,就是把tfunc()这个泛化版本中的所有模板参数都用具体的类型代替,构成一个特殊的版本(全特化版本),既然所有模板参数都用具体的类型代替了,那么tfunc()泛化版本中template后面尖括号中的内容就变成空了。
|
|
全特化实际上等价于实例化一个函数模板,并不等价于一个函数重载
|
|
==调用优先级:普通函数 > 函数模板特化 > 函数模板泛化==
3.4.2 偏特化
(1)模板参数数量上的偏特化
What is : 特化第1个模板参数类型为double类型,但第2个模板参数不特化
实际上,从模板参数数量上来讲,函数模板不能偏特化,只有类模板才能偏特化
(2)模板参数范围上的偏特化
What is : 所谓“参数范围”,比如原来是int类型,如果变成const int类型,那么与int类型相比,const int类型的范围就变小了;再比如,如果原来是任意类型T,现在变成T *(从任意类型缩小为任意指针类型),那这个类型的范围也是变小了;还有T &(左值引用)、T&&(右值引用),对于T,从类型范围上都属于变小了。
对于函数模板,也不存在模板参数范围上的偏特化。因为这种所谓模板参数范围上的偏特化,实际上是函数模板的重载
|
|
3.5 实例化模板的返回值问题
- 显示实例化
|
|
- auto
- 使用auto结合decltype完成返回值类型推断
|
|
三、特异的语法
1、省略参数
不管是类型模板参数还是非类型模板参数,如果在代码中没有用到这个参数,则参数名可以省略
|
|
可以做如下省略
|
|
2、“无用"的typename
类型前面可以增加一个typename修饰以明确标识一个类型。有的时候为了表明其后面是一个类型,也需要用typename修饰
|
|
四、模板函数在工程中
1、inline和constexpr的含函数模板
inline或 constexpr说明符放在模板参数列表之后,返回类型之前:
|
|
2、编写与类型无关的代码
|
|
这样写的好处
const T &
作为函数参数可以避免实参是不可调用类型less<T>()
使用标准库可以避免有些类型没有定义>
的比较