目录
第12章 特化与重载
目前为止,我们已经知道了:C++模板如何使一个泛型定义扩展成一些相关的类家族或者函数家族。虽然这是一个功能很强大的机制,但该机制并非适合于所有的情况;在一些情况下,这种泛型操作就不是特定模板参数替换的最佳选择。
12.2 重载函数模板
两个同名的函数模板可以同时存在,还可以对它们进行实例化,使它们具有相同的参数类型。下面是另一个简单的例子:
template<typename T>
int f(T)
{
return 1;
}
template<typename T>
int f(T*)
{
return 2;
}
如果我们用int*来替换第1个模板的T,用int来替换第2个模板的T,那么将会获得两个具有相同参数类型(和返回类型)的同名函数。也就是说,不仅是同名模板可以同时存在,它们各自的实例化体也可以同时存在,即使这些实例化体具有相同的参数类型和返回类型。
12.2.1 签名
只要具有不同的签名,两个函数就可以在同一个程序中同时存在。我们对函数的签名定如下:
1.非受限函数的名称(或者产生自函数模板的这类名称)。
2.函数名称所属的类作用域或者名字空间作用域;如果函数名称是具有内部链接的,还包括该名称声明所在的翻译单元。\
3.函数的const、volatile或者const volatile限定符(前提是它是一个具有这类限定符的成员函数)。
4.函数参数的类型(如果这个函数是产生自函数模板的,那么指的是模板参数被替换之前的类型)。
5.如果这个函数是产生自函数模板,那么包括它的返回类型。
6.如果这个函数是产生自函数模板,那么包括模板参数和模板实参。
这就意味着:从原则上讲,下面的模板和它们的实例化体可以在同个程序中同时存在:
template<typename T1, typename T2>
void f1(T1, T2);
template<typename T1, typename T2>
void f1(T2, T1);
template<typename T>
long f2(T);
template<typename T>
char f2(T);
然而,如果上面这些模板是在同一个作用域中进行声明的话,我们可能不能使用某些模板,因为实例化过程可能会导致重载二义性。例如:
template<typename T1, typename T2>
void f1(T1, T2)
{
std::cout << "f1(T1, T2)\n";
}
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)\n";
}
// 到这里为止一切都是正确的
int main()
{
f1<char, char>('a', 'b'); // 错误:二义性
}
在上面的代码中,虽然函数f1<T1 = char, T2 = char>(T1,T2)可以和函数f1<T1 = char, T2 =char>(T2, T1)同时存在,但是重载解析规则将不知道应该选择哪一个函数。
12.2.2 重载的函数模板的局部排序
template<typename T>
int f(T)
{
return 1;
}
template<typename T>
int f(T*)
{
return 2;
}
int main()
{
std::cout << f(0) << std::endl;
std::cout << f((int*)0) << std::endl;
}
让我们先考虑调用(f(0)):实参的类型是int,如果用int替换T,就能和第1个模板的参数匹配。然而,第2个模板的参数类型总是一个指针;因此,经过演绎之后,只有产生自第1个模板的实例才是该调用的候选函数。在这个调用中,重载解析并没有发挥作用。
第2个调用((f ( (int*) 0) )就显得比较有趣:对于这两个模板,实参演绎都可以获得成功,于是就获得两个函数,即f<int*>(int*)和f<int>(int*)。如果根据原来的重载解析观点,这两个函数和实参类型为 int*的调用的匹配程度是一样的,这也就意味着该调用是二义性的(见附录B)。然而,在这种情况下,还应该考虑重载解析的额外规则:选择“产生自更特殊的模板的函数”。因此(我们将在后面的小节看到),第2个模板被认为是更加特殊的模板,从而(再次)产生下面的输出结果:
1
2
12.2.3 正式的排序原则
接下来,我们将给出一个精确的过程,它能够判断:在参与重载集的所有函数模板中,某个函数模板是否比另一个函数模板更加特殊。然而,我们应该知道这只是不完整的排序原则:就是说,两个模板也可能会被认为具有相同的特殊程度。如果重载解析必须在这两个特殊程度相同的模板中进行选择,那么将不能做出任何决定,也就是说程序包含了一个二义性错误。
假设我们要比较两个同名的函数模板ft1和ft2,对于给定的函数调用,它们看起来都是可行的。在我们下面的讨论中,对于没有被使用的缺省函数实参和省略号参数,我们将不考虑。接下来,通过如下替换模板参数,我们将为这两个模板虚构两份不同的实参类型(如果是转型函数模板,那么还包括返