模板与泛型编程
--重载与函数模板
引言:
函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。
但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性。
一、函数匹配与函数模板
如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下:
1.为这个函数名建立候选函数集合,包括:
a.与被调用函数名字相同的任意普通函数。
b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参。
2.确定哪些普通函数是可行的(如果有可行函数的话)。候选集合中的每个模板实例都可行的,因为模板实参推断保证函数可以被调用。
3.如果需要转换来进行调用,根据转换的种类排列可靠函数,记住,调用模板函数实例所允许的转换是有限的。
a.如果只有一个函数可选,就调用这个函数。
b.如果调用有二义性,从可行函数集合中去掉所有函数模板实例。
4.重新排列去掉函数模板实例的可行函数。
a.如果只有一个函数可选,就调用这个函数。
b.否则,调用有二义性。
二、函数模板匹配的例子
template <typename T> int compare(const T &,const T &); template <class U,class V> int compare(U,U,V); int compare(const char *,const char *);
重载集合包含三个函数:第一个模板处理简单值,第二个模板比较两个序列的元素,第三个是处理C风格字符串的普通函数。
三、确定重载函数模板的调用
可以在不同类型上调用这些函数:
compare(1,0); vector<int> ivec1(10),ivec2(20); compare(ivec1.begin(),ivec1.end(),ivec2.begin()); int ia1[] = {0,1,2,3,4,5,6,7,8,9}; compare(ia1,ia1 + 10,ivec1.begin()); const char const_arr1[] = "world",const_arr2[] = "hi"; compare(const_arr1,const_arr2); char ch_arr1[] = "world",ch_arr2[] = "hi"; compare(ch_arr1,ch_arr2);
compare(1,0):
两个形参都是int类型。候选函数是第一个模板将T绑定到 int的实例
化,以及名为compare的普通函数。但该普通函数不可行——
不能将 int对象传给期待char*对象的形参。用int实例化的函数与该调用完全匹配,所以选择它。
compare(ivec1.begin(),ivec1.end(),
ivec2.begin()) :
compare(ia1,ia1+
10,ivec1.begin()):
这两个调用中,唯一可行的函数是有三个形参的模板的实例化。带两个参数的模板和普通非模板函数都不能匹配这两个调用。
compare(const_arr1,const_arr2:
这个调用正如我们所期待的,调用普通函数。该函数和将T绑定到 constchar* 的第一个模板都是可行的,也都完全匹配。根据规则(3)b,会选择普通函数。从候选集合中去掉模板实例,只剩下普通函数可行。
compare(ch_arr1,ch_arr2):
这个调用也绑定到普通函数。候选者是将T绑定到 char*的函数模板的版本,以及接受constchar*实参的普通函数,两个函数都需要稍加转换将数组ch_arr1和 ch_arr2转换为指针。因为两个函数一样匹配,所以普通函数优先于模板版本。
四、转换与重载的函数模板
设计一组重载函数,其中一些是模板而另一些是普通函数,这可能是困难的。这样做需要深入理解类型之间的关系,具体而言,就是当设计模板时,可能发生和不能发生的隐式转换。如:
char *ch_arr1 = "world",*ch_arr2 = "hi"; compare(ch_arr1,ch_arr2);
这个调用模板版本匹配!通常,我们希望无论是传递数组,还是传递指向该数组元素的指针,都获得同一函数。但是,在这个例子中,将char*绑定到 T的函数模板与该调用完全匹配。普通版本仍然需要从char*到 constchar
* 的转换,所以优先选择函数模板。
另一个具有惊人结果的改变是,如果compare的模板版本有一个T类型的形参代替T的 const引用,会发生的情况:
template <typename T> int compare(T,T);
如果有一个普通类型的数组,则无论传递数组本身,还是传递指针,都将调用模板版本。调用非模板版本的唯一途径是实参是constchar 或constchar
* 指针数组的时候:
//调用compare(T,T)版本 char ch_arr1[] = "world",ch_arr2[] = "hi"; compare(ch_arr1,ch_arr2); char *p1 = "world",*p2 = "hi"; compare(p1,p2); //调用compare(const char *,const char *)版本 const char const_arr1[] = "world",const_arr2[] = "hi"; compare(const_arr1,const_arr2); const char *cp1 = const_arr1,*cp2 = const_arr2; compare(cp1,cp2);
这些情况下,普通函数和函数模板完全匹配。当匹配同样好时,非模板版本优先。
【最佳实践】
设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为可能会使函数的用户感到奇怪,
因此:定义函数模板特化几乎总是比使用非模板版本更好!
//P573 习题16.61/62 template <class U,class V> int compare(U,U,V) { cout << "U,U,V" << endl; return 0; } int compare(const char *,const char *) { cout << "cosnt char *" << endl; return 0; } template <typename T> int compare(T,T) { cout << "T,T" << endl; return 0; } int main() { char ch_arr1[] = "world",ch_arr2[] = "hi"; const char const_arr1[] = "world",const_arr2[] = "hi"; compare(ch_arr1,const_arr1); //const char * compare(ch_arr2,const_arr2); //const char * compare(0,0); //T,T }
//习题16.63 template <class T> T calc(T,T) { cout << "T,T" << endl; } double calc(double,double) { cout << "double,double" << endl; } template <> char calc<char>(char,char) { cout << "char,char" << endl; } int main() { int ival; double dval; float fd; calc(0,ival); //T,T calc(0.25,dval); //double,double calc(0,fd); //double,double calc(0,'J'); //double,double }