首页 > 代码库 > C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程

--重载与函数模板

引言:

函数模板可以重载:可以定义有相同名字参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数

但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性。



一、函数匹配与函数模板

如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下:

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_arr1ch_arr2转换为指针。因为两个函数一样匹配,所以普通函数优先于模板版本



四、转换与重载的函数模板

设计一组重载函数,其中一些是模板而另一些是普通函数,这可能是困难的。这样做需要深入理解类型之间的关系,具体而言,就是当设计模板时,可能发生和不能发生的隐式转换。如:

    char *ch_arr1 = "world",*ch_arr2 = "hi";
    compare(ch_arr1,ch_arr2);

这个调用模板版本匹配!通常,我们希望无论是传递数组,还是传递指向该数组元素的指针,都获得同一函数。但是,在这个例子中,char*绑定到 T函数模板与该调用完全匹配。普通版本仍然需要从char*constchar * 转换,所以优先选择函数模板。

另一个具有惊人结果的改变是,如果compare的模板版本有一个T类型的形参代替Tconst引用,会发生的情况:

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
}