【C++基础】第三十九课:函数匹配

函数匹配,候选函数,可行函数

Posted by x-jeff on April 1, 2022

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.函数匹配

在大多数情况下,我们容易确定某次调用应该选用哪个重载函数。然而,当几个重载函数的形参数量相等以及某些形参的类型可以由其他类型转换得来时,这项工作就不那么容易了。例如:

1
2
3
4
5
void f();
void f(int);
void f(int,int);
void f(double,double=3.14);
f(5.6);//调用void f(double,double)

1.1.确定候选函数和可行函数

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。

第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数(viable function)。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。

如果没找到可行函数,编译器将报告无匹配函数的错误。

1.2.寻找最佳匹配(如果有的话)

函数匹配的第三步是从可行函数中选择与本次调用最匹配的函数。其基本思想是:实参类型与形参类型越接近,它们匹配得越好。

1.3.含有多个形参的函数匹配

当实参的数量有两个或更多时,函数匹配就比较复杂了。比如:

1
f(42,2.56)

选择可行函数的方法和只有一个实参时一样,编译器选择那些形参数量满足要求且实参类型和形参类型能够匹配的函数。此例中,可行函数包括f(int,int)f(double,double)。接下来,编译器依次检查每个实参以确定哪个函数是最佳匹配。如果有且只有一个函数满足下列条件,则匹配成功:

  • 该函数每个实参的匹配都不劣于其他可行函数需要的匹配。
  • 至少有一个实参的匹配优于其他可行函数提供的匹配。

如果在检查了所有实参之后没有任何一个函数脱颖而出,则该调用是错误的。编译器将报告二义性调用的信息。

编译器最终将因为上述例子的调用具有二义性而拒绝其请求:因为每个可行函数各自在一个实参上实现了更好的匹配,从整体上无法判断孰优孰劣。

2.实参类型转换

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体排序如下所示:

  1. 精确匹配,包括以下情况:
    • 实参类型和形参类型相同。
    • 实参从数组类型或函数类型转换成对应的指针类型。
    • 向实参添加顶层const或者从实参中删除顶层const。
  2. 通过const转换实现的匹配。
  3. 通过类型提升实现的匹配。
  4. 通过算术类型转换或指针转换实现的匹配。
  5. 通过类类型转换实现的匹配。

2.1.需要类型提升和算术类型转换的匹配

分析函数调用前,我们应该知道小整型一般都会提升到int类型或更大的整数类型。假设有两个函数,一个接受int、另一个接受short,则只有当调用提供的是short类型的值时才会选择short版本的函数。有时候,即使实参是一个很小的整数值,也会直接将它提升成int类型;此时使用short版本反而会导致类型转换:

1
2
3
void ff(int);
void ff(short);
ff('a');//char提升成int;调用f(int)

所有算术类型转换的级别都一样。例如,从int向unsigned int的转换并不比从int向double的转换级别高。举个具体点的例子,考虑:

1
2
3
void manip(long);
void manip(float);
manip(3.14);//错误:二义性调用

字面值3.14的类型是double,它既能转换成long也能转换成float。因为存在两种可能的算数类型转换,所以该调用具有二义性。

2.2.函数匹配和const实参

如果重载函数的区别在于它们的引用类型的形参是否引用了const,或者指针类型的形参是否指向const,则当调用发生时编译器通过实参是否是常量来决定选择哪个函数:

1
2
3
4
5
6
7
Record lookup(Account&);
Record lookup(const Account&);
const Account a;
Account b;

lookup(a);//调用lookup(const Account&)
lookup(b);//调用lookup(Account&)

指针类型的形参也类似。如果两个函数的唯一区别是它的指针形参指向常量或非常量,则编译器能通过实参是否是常量决定选用哪个函数:如果实参是指向常量的指针,调用形参是const*的函数;如果实参是指向非常量的指针,调用形参是普通指针的函数。