【深度C++】之“函数匹配”

0. 什么是函数匹配

当我们调用一个函数时,多数情况下是确定的一项操作。当包含有重载的函数时,事情就变得复杂了。

回想【深度C++】之“函数重载”第2节,函数匹配(function matching) 正是执行这样的工作。它也叫做重载确定(overload resolution)

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

/// ***************** ///
/// 记住这个例子,一直用 ///
/// ***************** ///

void f();  // 1号
void f(int);  // 2号
void f(int, int);  // 3号
void f(double, double=3.14);  // 4号

f(5.6);
// 调用的是f(double, double)

可以把函数匹配的过程,理解为编译器的一段代码,用来确定调用处执行的是哪个函数的过程。

代码的输入是调用处的函数名、实参列表等调用信息;输出是被调用的函数入口。C++标准严格规定了函数匹配功能的规则和步骤。

关于函数匹配,分为3个步骤:

  1. 确定候选函数(candidate function),是一个列表;
  2. 确定可行函数(viable function),是一个列表;
  3. 确定最佳匹配(best match),是最终结果,如下描述的3种情况。

第3步输出的结果只有3种情况:

  1. 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。
  2. 编译器找不到一个函数与调用的实参匹配,此时编译器发出无匹配(no match) 的错误信息。
  3. 编译器找到多于一个函数可以匹配,但是每一个都不是明显的最佳选择,称为二义性调用(ambiguous call) 错误。

1. 如何确定候选函数

确定候选函数,只看两点:同名可见

  1. 与被调函数同名
  2. 其声明在调用点可见

本例中,有4个名字为f的候选函数。

若候选函数列表为空,编译器报无匹配错误。

2. 如何确定可行函数

得到候选函数后,基于候选函数列表,确定可行函数,有两个要求:

  1. 形参数量与本次调用提供的实参数量相同
  2. 每个实参的类型与对应的形参类型相同,或者能转换成形参类型

经过第1步,我们能确定1号、3号不是我们希望调用的函数,它们的参数数量要求是0个和2个。4号虽然是两个形参,但是有1个默认实参,因此可以。

经过第2步,我们能确定2号、4号都是可行函数。实参类型是double,可以转换成int,因此2号可行。4号的正好是double,可行。

若可行函数列表为空,编译器报无匹配错误。

注意,我们并没有考虑模板函数相关内容。

3. 如何确定最佳匹配

3.1 判别最佳匹配的规则

得到可行函数后,基于可行函数列表,确定最佳匹配函数。

这一步,编译器依次检查每个实参,并根据接下来介绍的最匹配原则给每个可行函数的每个实参进行等级排序。若能找到一个函数:

  1. 它的每个实参的排序都不劣于(不小于)其他可行函数;
  2. 它至少有一个实参的排序优于(大于)其他可行函数。

则找到了一个最佳匹配函数

若找不到最佳匹配函数,则报二义性错误。

3.2 最匹配原则

编译器给每个可行函数的每个实参等级排序的时候,根据下面的原则进行。

为了方便叙述,我们引入一种打分的机制:

:《C++ Primer(第5版)》中提供的方法,没有提到“打分”这一术语,这是编者为了方便理解自行添加的逻辑,不保证这是编译器实现的真正逻辑。)

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

拿之前的调用f(5.6)来举例,我们可以画一个表格:

传入实参 5.6
2号:void f(int)类型提升,3分
4号:void f(double, double=3.14)精确匹配,5分

因此编译器得到一个最佳匹配函数:void f(double, double=3.14),因为:

  1. 4号的每个参数的得分不小于2号
  2. 4号至少有一个实参的得分大于2号

:上述关于类型转换相关的知识,请参考【深度C++】之“类型转换”

3.3 二义性错误

上述的例子比较简单,只有一个参数,我们考虑调用:

/// ***************** ///
///   和之前的声明一样   ///
/// ***************** ///

void f();  // 1号
void f(int);  // 2号
void f(int, int);  // 3号
void f(double, double=3.14);  // 4号

f(422.56);

一、确定候选函数:4个,1-4号均可

二、确定可行函数:2个,排除1号、2号,保留3号、4号

三、确定最佳匹配:画表格

传入实参 42传入实参 2.56
void f(int, int)精确匹配,5分算术转换,2分
void f(double, double=3.14)类型提升,3分精确匹配,5分

此时问题来了,根据之前的准则:

  1. 它的每个实参的得分都不小于其他可行函数;
  2. 它至少有一个实参的得分大于其他可行函数。

我们找不到符合上述两条要求的函数,编译器最终将因为这个调用具有二义性错误而拒绝。

4. 总结

函数匹配,也称作重载确定,它是一个过程,主要任务是找到一个与实参最佳匹配的函数。多数情况下用来处理重载函数的调用。

函数匹配共有3个主要步骤:①确定候选函数②确定可行函数③确定最佳匹配函数。

第③步引入了一个等价排序原则,给每个可行函数的实参进行排序。

函数匹配过程有两个错误:①无匹配②二义性错误。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值