函数模板的定义
template <typename T>
void Swap(T &a, T &b){
T temp;
temp = a;
a = b;
b = temp;
}
函数模板允许我们以泛型的方式编写程序。在写法上,函数模板与普通函数的区别就在于函数定义的开头多了一行对于参数化类型的设置,其中关键字typename也可以替换为class,二者没有区别;并且我们也可以在尖括号内设置多个参数化类型。
在功能上,函数模板实际上为我们自动实现了参数类型或者返回值类型不同的函数的重载(注意函数重载不能通过返回值类型的不同来区分)。另外像重载常规函数那样,重载模板函数也是可以的,比如下面的函数定义就是对上面的函数模板的一个重载。总的来说,对于一个给定的函数名,可以同时有它的非模板函数、模板函数、显示具体化模板函数(将在后面介绍)以及它们的重载版本。
template <typename T>
void Swap(T &a, T &b, int n){
for(int i = 0;i < n;++i){
T temp;
temp = a;
a = b;
b = temp;
}
}
函数模板的使用
1、调用优先级
按照之前的介绍,在一个程序中可以同时存在同一个函数名的多个不同函数,其中有普通的函数,也有模板函数。遇到这种情况,编译器会按照如下原则进行调用。
优先级:非模板函数 >显式具体化模板函数>模板
显式具体化模板函数,顾名思义,就是在定义模板函数的时候具体指明了参数化类型是什么。其声明方式如下:
template <> void Swap<int>(int &a, int &b);//或
template <> void Swap(int &a, int &b);
显式具体化模板函数的作用是什么呢?从上面的示例中看,它好像没什么卵用。上面的声明只不过是把参数化类型改成了int,调用它们和调用普通的模板没什么区别。的确是这样,但是思考一下,如果我们给Swap函数传入的参数是类类型的呢?这时交换参数的操作就需要一些特殊处理。这时显式具体化模板函数就发挥作用了。显式具体化模板函数将使用自己的函数定义来进行实例化。
class Book{
int price;
int id;
}
//显式具体化函数模板
template <> void Swap<Book>(Book &a, Book &b){
//与普通的交换不同,只交换了price的值
int temp = a.price;
a.price = b.price;
b.price = temp;
}
int main(){
int i,j;
Swap(i,j);//将调用函数模板原型
Book b1,b2;
Swap(b1,b2);//将调用显式具体化函数模板
}
2、函数模板的实例化
实例化是指为一个不确定参数具体类型的模板生成具体明确的函数的定义的过程。例如在刚才的代码中,我们调用Swap(i, j),这实际上就是对模板函数template <typename T> void Swap(T &a, T &b)进行实例化,这种实例化被称为隐式实例化。那么相对应的就有显示实例化:
template void Swap<int>(int &a, int &b);
乍一看好像和显示具体化模板没啥区别,确实很像。区别在于,第一,显示实例化的关键字template后面没有<>。第二,显示实例化没有额外的函数定义,它利用函数模板原型的定义进行实例化。
那问题来了,这种显示实例化有什么特别的作用吗?看如下代码(如下代码在VS2013,VS2015环境下无法通过编译,难道是不支持?)
//显式实例化
template void Swap<double>(double &a, double &b);
int main(){
int i;
double j;
//因为i,j的类型不一致,而函数模板原型中的类型是一致的,
//因此将调用显式实例化函数模板,将i强制转换为double
Swap<double>(i,j);
}
需要注意的是,在同一个文件中出现同一种类型的显示实例化和显式具体是不允许的。
3、补充
在编写函数模板时,我们可能会遇到如下困境:
template <typename T1, typename T2>
void Swap(T1 &a, T2 &b){
? c = a + b;//c该定义成什么类型呢?
}
对此我们可以通过C++11新增的decltype来解决:
decltype(a + b) c = a + b;
还有可能遇到另一种困境:
template <typename T1, typename T2>
? Swap(T1 &a, T2 &b){//返回类型如何定义?
return a + b;
}
//此时还未声明a和b,下面的做法是错误的
template <typename T1, typename T2>
decltype(a + b) Swap(T1 &a, T2 &b){
return a + b;
}
对此,C++11提供了一种新的返回类型后置的函数声明和定义的方法
template <typename T1, typename T2>
auto Swap(T1 &a, T2 &b) -> decltype(a + b)
{
return a + b;
}
此外,如果编译器支持C++14,也可以直接写成:
template <typename T1, typename T2>
auto Swap(T1 &a, T2 &b)
{
return a + b;
}
小结
函数模板为我们重载函数提供了极大的便利,有很多背后的工作(如何选择最优的同名函数版本,这个过程被称为重载解析,涉及的内容还是非常多的,这里就不再详细介绍)都由编译器为我们自动完成了。