函数模板
基本语法
有时候对于多种数据类型可能需要执行相同的操作,如果没有函数模板,则需要为各个数据类型单独实现相应的函数,这样会导致代码的编写非常繁琐。有了函数模板之后只需要为所有的类型实现一个公用的函数模板即可,编译器会根据该模板为不同的类型生成相应的函数。我们声明一个函数模板的时候不创建函数,只是提供创建函数的模板,当需要使用到函数的时候,编译器才会根据模板创建函数
。
下面是声明一个函数模板的基本格式:
template <class T> //这里class也可以用typename代替,两者的效果一样
void swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
int ia = 0, ib = 1;
double da = 0, db = 1;
swap(ia, ib); //传入类型为int
swap(da, db); //传入类型为double
printf("ia = %d, ib = %d\n", ia, ib);
printf("da = %f, db = %f\n", da, db);
重载的模板
有的时候对于不同的参数需要采用不同的算法,这个时候就需要使用重载的模板。与普通函数的重载类似,模板的重载只需要保持模板的函数名相同,而签名不同即可。例如:
template <class T>
void swap(T &a, T &b); //#1
template <class T>
void swap(T * a, T * b, int n) //#2
int a = 0, b = 1;
int ar[] = {0, 0, 0}, br[] = {1, 1, 1};
swap(a, b); //#1
swap(ar, br, 3); //#2
显式具体化
如果对所有的数据类型都采用同一个模板,可能会导致一些问题,例如如果定义一个sum()
函数表示将两个数据相加,使用运算符+
实现,这对基本数据类型管用,但对于高级的数据类型,可能并没有定义+
的操作。例如:
template <class T>
T sum(T a, T b){
return a + b;
}
struct car
{
char brand [20];
double price;
};
上述函数对int
类型可以很好地运行,但是对于一个结构体car
则会出错,所以我们可以使用显式具体化这一特性来针对结构体car
单独实现特定的操作。显示具体化在声明的时候必须以template <>
开头,例如:
template <> car sum<car>(car a, car b){ //<car>可以省略
a.price += b.price;
return a;
}
对于同一个函数名如果有非模板函数,模板函数和显式具体化函数,则它们的优先级为非模板函数
>显式具体化函数
>模板函数
。
实例化和具体化
另一个容易与显示具体化混淆的操作是显示实例化,显示实例化表示的是给定模板需要的数据类型,从已有的函数模板中实例化出一个函数。实例化出来的函数中执行的算法,已经在函数模板中给出,而具体化的时候需要重新实现函数中执行的算法。例如:
template <class T>
T sum(T a, T b){
return a + b;
}
//实例化,不需要自己的实现,算法已由模板给出
template sum<int>(int a, int b);
//具体化,函数的算法需要自己的实现
template <> car sum<car>(car a, car b){ //<car>可以省略
a.price += b.price;
return a;
}
除了单独用一个语句实现实例化,还可以在函数调用的时候显式实例化,例如:
int a = 1;
double b = 1;
printf("a + b = %f", sum<double>(a , b));
注意这里a跟b不属于一个数据类型,所以如果不显示实例化函数,编译器将找不到与该调用匹配的模板
。
编译器选择使用哪个函数版本
当编译器发现有多个可以选择的函数版本的时候,其选择的优先级是:
- 完全匹配,但普通函数高于模板
- 提升转换,如char和short转换为int
- 标准转换,如int转换为char,long转换为double
- 用户定义的转换,如类声明中定义的转换
decltype
的作用
有时候在声明模板的时候没有办法事先确定一个变量的类型,例如:
template <class T1, class T2>
void ft(T1 x, T2 y){
T1 z = x + y;
}
其实这里z
的类型不一定为T1
,例如当T1为int
,T2为double
时,z
的类型为double
,而不是跟T1
一样的int
。这个时候我们就可以使用decltype
关键字来确定z
的类型。decltype
关键字的用法为:
decltype(expression) var;
变量var
的数据类型根据expression
确定。其规则如下:
- 如果
expression
是一个没有用括号括起的变量,则var
的类型与该变量一致
double x = 5.5;
decltype(x) var; //double
- 如果
expression
是一个函数调用,则var
的类型为该函数返回值的类型,注意被调用的函数不会被执行,只是要获取其返回值的类型
int f(int);
decltype(f(3)) var; //int
- 如果
expression
是一个用括号括起来的变量,则var
是指向该类型的引用
double x = 5.5;
decltype((x)) var; //double &
- 如果
expression
是都不满足上述条件,则var
跟expression
相同
decltype(100) var; //int
后置返回类型
最后还有一种情况是decltype
关键字没有办法解决的,就是当函数的返回值不能确定时,例如:
template <class T1, class T2>
T1 sum(T1 x, T2 y){
return x + y;
}
这里并不能直接确定x
与y
相加之后的类型为T1
,例子与上述相似。但是又不能通过decltype
解决,因为这个时候x
和y
还没有被声明,所以不能使用。这个时候就可以使用另一种方式声明函数的返回类型——后置返回类型:
template <class T1, class T2>
auto sum(T1 x, T2 y) -> decltype(x + y){
return x + y;
}
这里的auto
只是一个占位符,其类型由后置返回类型decltype(x + y)
提供。