此篇只讲函数模板,主要有一下几点:
- 模板的编译
- 函数的具体化
- 编译器如何选择函数的版本
- 何为最具体
- decltype与后置返回类型
模板的编译
编译生成的最终代码并不会包含函数模板,也就是函数模板无法减少程序的代码量,它主要的功能是泛型编程,编写一个通用的函数/类/等,减少编程的工作量。
当模板函数遇到int型参数时,编译器会编译出一个int版本的函数;同理,当遇到double类型时,会生成double类型函数。
最终有多少输入的参数类型,模板就会被分别编译成多少个参数不同的函数!!!
函数的具体化
隐式实例化、显式实例化、显式具体化统称为具体化!!!
下面分别给出模板正常声明、显式具体化声明、显式实例化、隐式实例化的方法:
- 正常声明
template <typename T>
void swap(T a, T b);
- 显式具体化声明
template <> void swap <int> (int a, int b);
//第二种方法,不建议用下面这种
template <> void swap(int a, int b);
显式具体化的含义是,不要使用swap()模板来生成函数定义,应使用int类型显式的定义函数的定义!!
- 实例化
无论显式实例化还是隐式实例化,都是由函数模板生成函数的定义,只是生成的时间不一样!! - 显示实例化
- 在编译时实例化
显式实例化的声明方法如下:
- 在编译时实例化
template void swap <int> (int a, int b);
//下面是调用时显式的实例化
swap<int>(a, b);
显式实例化是命令编译器利用函数模板生成函数实例,其时间是遇到显式实例化的声明时,或遇到显式实例化的调用时!!
- 隐式实例化
- 在运行时实例化,影响运行速度
隐式实例化就是因为函数的调用,传入函数的参数,根据参数类型,利用函数模板生成函数的定义。
- 在运行时实例化,影响运行速度
- 在同一函数中试图使用同一种类型的显式具体化和显式实例化将会出错。
编译器选择哪个版本的函数
这个过程称之为重载解析,其过程如下:
- 创建候选函数列表,包含名称相同的函数及模板函数;
- 用上列表创建可执行函数列表,包含函数的隐式转换;
- 确定是否有最佳的可行函数;
最佳可行函数的优先级如下:
- 完全匹配,常规函数优于模板;
- 提升转换(char/short转int,float转double);
- 标准转换(int转char,long转double);
- 用户定义的转换(类声明中定义的转换,没懂);
完全匹配允许的无关紧要的转换表(C++ primer plus P290):
实参 | 形参 |
---|---|
T | T & |
T & | T |
T [] | * T |
T (argument-list) | T (*) (argument-list) |
T | const T |
T | volatile T |
T * | const T |
T * | volatile T * |
当有多个完全匹配时,模板更具体化的函数优先被选中。
当有多个匹配的原型时(非模板),编译器无法完成重载解析。
指向非const数据的指针与引用,优先与非const指针,引用参数匹配。
更具体化
举个例子,如下:
template <typename T> void func(T a); // #1
template <typename T> void func(T *a); // #2
struct blot{...}; //声明结构体
blot ink {...}; //定义结构体ink
func(&ink); //调用函数
func函数与#1匹配时,T被解释成(blot *),隐式实例为:
func<blot *>(blot *); //#a
func函数与#2匹配时,T被解释成(blot),隐式实例为:
func<blot>(blot *); //#b
在这两个实例中,#b被认为更具体,因为其参数解释为指向T的指针,其T已经被具体化为指针,因此说它更“具体”。(T转换成 blot 与 blot * 的差距)
最后注意,无论是更具体,还是具体,其都是完全匹配,优于类型转换(不包括无关类型转换)
decltype
用法:声明一个变量的类型
decltype(expression)var ;
需要满足以下三点:
- 若expression是无括号括起的标识符,var与该标识符类型相同(包括const);
- 若expression为函数调用,则var类型与函数返回值类型相同(但不会调用函数);
- 若expression为左值,且有括号括起,则var为标识符的引用类型(注意引用声明时必须初始化);若为右值,则回到第一点;
例子如下:
int x;
double y;
char * func();
int z;
// #1 第一种情况, a类型为(x+y)的类型
decltype (x + y) a;
// #2 第二种情况, b类型为char *
decltype (func()) b;
// #3 第三种情况,左值, c类型为int &,c为z引用
decltype ((x)) c = z;
// #4 第三种情况,右值,d类型为float
decltype ((1.0L)) d;
后置返回类型
声明方式,很简单
template <typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x + y)
{
return x + y;
}
主要是因为返回时,无法预先知道函数的返回类型,auto是一个占位符,表示后置返回类型提供的类型。