#include<iostream>
#include<cstdio>
using namespace std;
template<class T>
T add(T a, T b);
int main()
{
int a, b;
cin >> a >> b;
double c, d;
cin >> c >> d;
cout << add(a, b) << endl; //根据实参的具体类型创建相应的函数
cout << add(c, d) << endl;
}
template<class T>
T add(T a, T b)
{
return a + b;
}
该例子定义了一个函数模板,将类型命名为T(可以按自己喜好命名),称为泛型,因为它不是具体的类型。关键字class是必须的(可以用typename替换)。该模板并不创建任何函数,而只是定义了函数。当调用该函数模板时,编译器将按照模板创建相应的函数,用具体的类型替换掉T。需要注意的是,函数模板不能缩短可执行程序。对于上面这个例子来说,最终仍将有两个独立的函数定义。最终的代码不包含任何模板,而是包含了为程序生成的实际函数。
重载的模板
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
template<class T> //模板的原型
void print(T a);
template<class T>
void print(T a, T b);
int main()
{
string a, b;
cin >> a >> b;
print(a);
print(a, b);
return 0;
}
//模板重载,一个模板是输出形参,第二个模板是输出两个形参中较大者
template<class T>
void print(T a)
{
cout << a << endl;
}
template<class T>
void print(T a, T b)
{
if (a > b) cout << a << endl;
else cout << b << endl;
}
模板的局限性
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
struct NODE
{
int val1, val2;
};
template<class T>
T add(T a, T b);
int main()
{
int a = 1, b = 2;
NODE c, d;
c.val1 = c.val2 = 1;
d.val1 = d.val2 = 1;
cout << add(a, b) << endl; //运行正确
NODE sum=add(c,d); //运行出错,编译器不知道两个结构相加应怎么处理
return 0;
}
template<class T>
T add(T a, T b)
{
return a + b;
}
总之,函数模板很可能无法处理某种类型的数据。当然可以对特定运算符重载,另一种解决方法是提供具体化的模板定义。
显示具体化
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
struct NODE
{
int val1, val2;
};
template<class T>
T add(T a, T b);
template <> NODE add(NODE a, NODE b); //相应的原型
int main()
{
int a = 1, b = 2;
NODE c, d;
c.val1 = c.val2 = 1;
d.val1 = d.val2 = 1;
cout << add(a, b) << endl; //运行正确
NODE sum = add(c, d);
cout << sum.val1 << " " << sum.val2 << endl;
return 0;
}
template<class T>
T add(T a, T b)
{
return a + b;
}
template <> NODE add(NODE a, NODE b) //显示具体化,为特定类型提供具体化的模板定义
{
NODE sum;
sum.val1 = a.val1 + b.val1;
sum.val2 = a.val2 + b.val2;
return sum;
}
实例化和具体化
首先先说一下隐示实例化。函数模板本身并不会生成函数定义,只是用于生成函数定义的模板。编译器使用函数模板为特定类型生成函数定义时,得到的是模板实例。例如,当调用函数模板时,编译器会根据参数的类型生成特定的函数定义,这就是隐式实例化。有隐式实例化,也就有显示实例化。显示实例化实际上就是加多一条为特定数据类型生成具体函数定义的语句。当调用函数模板时,若这种类型已经经过显示实例化了,则直接调用,编译器不需要再自动生成相应的函数定义。具体化就是前面所说的,为特定类型定义具体函数内部的操作。
#include<iostream>
#include<cstdio>
using namespace std;
struct NODE
{
int val1, val2;
};
template<class T> //函数模板原型
T add(T a, T b);
template <> NODE add(NODE a, NODE b); //显示具体化原型
template double add<double>(double a, double b); //为double声明的显示实例化,<double>表示把double替换掉T
int main()
{
int int_a, int_b;
double double_a, double_b;
NODE NODE_a, NODE_b;
cin >> int_a >> int_b;
cin >> double_a >> double_b;
cin >> NODE_a.val1 >> NODE_a.val2;
cin >> NODE_b.val1 >> NODE_b.val2;
cout << add(int_a, int_b) << endl; //编译器进行隐式实例化
cout << add(double_a, double_b) << endl; //调用前面显示实例化过的函数定义
NODE sum = add(NODE_a, NODE_b); //调用显示具体化的函数定义
cout << sum.val1 << " " << sum.val2 << endl;
}
template<class T> //函数模板
T add(T a, T b)
{
return a + b;
}
template <> NODE add(NODE a, NODE b) //显示具体化,为NODE类型定义具体函数
{
NODE sum;
sum.val1 = a.val1 + b.val1;
sum.val2 = a.val2 + b.val2;
return sum;
}
需要注意的优先级是:非模板函数优先于模板函数,而模板函数中的具体化优先于实例化,显示实例化又优先于隐式实例化。
编译器如何选择函数
#include<iostream>
#include<cstdio>
using namespace std;
template<class T>
void Swap(T a, T b);
template<class T>
void Swap(T *a, T *b);
int main()
{
int a = 1, b = 2;
Swap(a, b); //输出1
Swap(&a, &b); //输出2
}
template<class T>
void Swap(T a, T b) //模板1
{
cout << "1" << endl;
}
template<class T>
void Swap(T *a, T *b) //模板2
{
cout << "2" << endl;
}
对于第一次调用,只有模板1是匹配的,所以输出1。对于第二次调用,参数是地址,事实上两个模板都是匹配的,对于模板1,把T解释成int *,而对于模板2,把T解释成int。既然都匹配,编译器到底会选择哪一个呢。原则是选择变换代价最小的。模板2只需要把T解释成int。所以最终将会调用模板2。
也可以创建自定义选择:
#include<iostream>
#include<cstdio>
using namespace std;
template<class T>
T Min(T a, T b);
int Min(int a, int b) //返回绝对值较小者
{
if (a < 0) a = -a;
if (b < 0) b = -b;
cout << "2" << endl;
return a < b ? a : b;
}
int main()
{
int a = 3;
int b = -4;
double c = 1.0;
double d = 2.9;
cout << Min(a, b) << endl; //调用具体函数,因为具体函数优先于模板函数
cout << Min(c, d) << endl; //调用模板函数,隐式实例化生成double类型的函数定义
cout << Min<>(a, b) << endl; //<>代表调用模板函数,并隐式实例化生成int类型的函数定义
cout << Min<int>(c, d) << endl; //调用模板函数,<int>代表显示实例化生成int类型的定义,并把c和d强行转换成int
return 0;
}
template<class T> //函数模板,返回较小值
T Min(T a, T b)
{
cout << "1" << endl;
return a < b ? a : b;
}
模板函数的发展
比如有如下语句
template<class T1,class T2>
void add(T1 a, T1 b)
{
? type ? sum = a + b;
...
}
sum到底定义成什么类型呢,如果a是double,b是int,则sum为double。如果a为char,b为int,则sum为int。因此sum的类型是根据a和b来确定的。因此事先我们是不知道的。
C++11新增关键字decltype提供了解决方案。
int x;
decltype(x) y; //decltype()里的参数可以是变量或表达式,表示y的类型和括号里的类型一致。
因此上述函数模板可以这么写
template<class T1,class T2>
void add(T1 a, T2 b)
{
decltype(a + b) sum; //定义了sum变量,sum的类型是a+b的类型
sum = a + b; //也可以合并 decltype(a+b) sum=a+b;
}
decltype里甚至可以是一个函数调用,例如:
#include<iostream>
#include<cstdio>
using namespace std;
int getVal(int x);
int main()
{
decltype(getVal(1)) a; //定义了变量a,a的类型和函数返回值的类型相同
//注意,这并不会实际调用函数,编译器通过函数原型来知道返回值类型
return 0;
}
int getVal(int x)
{
return x;
}
还需要注意的几点是
decltype(x) a1 = x; //a1类型是int,它的值和x的值一样
decltype((x)) a2 = x; //a2类型是int&,它是指向x的引用,它是x的别名
//这两者区别就是在于decltype参数里的x是否被括起来
typedef decltype(x) xxx; //还可以用typedef
xxx a, b, c, d;
如果一个函数模板返回值是两个参数的和,而这两个参数的类型又不一样呢,
template<class T1,class T2>
? type ? add(T1 a, T2 b)
{
return a + b;
}
?type?能不能写decltype(a+b)呢。答案是不行的。如果把返回类型设置成decltype(a+b),此时还未声明参数a和b,编译器看不到它们,也就无法使用它们。正确写法是
template<class T1,class T2>
auto add(T1 a, T2 b)->decltype(a + b)
{
return a + b;
}
auto相当于占位符,具体类型后面指出,因为执行到后面的时候,已经声明形参,也就可以用了。