最佳匹配原则

一般来说,精确匹配肯定比需要类型转换的匹配要更好,但是当形参有多个,并且无法完全精确匹配的时候,要确定最佳匹配函数就有点困难了。
但是有下面的原则:

  • 函数的每个实参的匹配都不能比其他可行函数更差
  • 函数至少有一个实参的匹配要比其他可行函数更好

那么问题又来了,什么是更好,什么又是更差呢?编译器将实参类型到形参类型的转换划分了等级:

  • 精确匹配,包括实参类型和形参类型相同,实参从数组或函数转换成对应的指针类型,向实参添加顶层const或从实参删除顶层const
  • 通过const转换实现的匹配
  • 通过类型提升实现的匹配
  • 通过算数类型转换实现的匹配
  • 通过类类型转换实现的匹配

最佳匹配原则

一般来说,精确匹配肯定比需要类型转换的匹配要更好,但是当形参有多个,并且无法完全精确匹配的时候,要确定最佳匹配函数就有点困难了。
但是有下面的原则:

  • 函数的每个实参的匹配都不能比其他可行函数更差
  • 函数至少有一个实参的匹配要比其他可行函数更好

那么问题又来了,什么是更好,什么又是更差呢?编译器将实参类型到形参类型的转换划分了等级:

  • 精确匹配,包括实参类型和形参类型相同,实参从数组或函数转换成对应的指针类型,向实参添加顶层const或从实参删除顶层const
  • 通过const转换实现的匹配
  • 通过类型提升实现的匹配
  • 通过算数类型转换实现的匹配
  • 通过类类型转换实现的匹配

等级越前,匹配也就越好

通过const转换实现的匹配

所谓通过const转换实现的匹配,指的是通过加const限定词,能够与可行函数精确匹配。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
//函数1
/*
int f(string &a)
{
cout<<"call function 1"<<endl;
return 0;
}*/
//函数2
int f(const string &a)
{
cout<<"call function 2"<<endl;
return 0;
}
int main()
{
string test = "test";
f(test);
return 0;
}

在这里,test可以通过const转换,从而匹配函数2,将能够找到最佳匹配函数2(当前情况它也只有一个可选了)。
运行结果如下:
call function 2
如果把函数1的注释去掉再运行,就会发现,虽然第一个调用既能匹配函数1,也能匹配函数2,但是由于匹配函数2的时候,需要const转换,因此比精确匹配要差,最终,它会调用函数1。
去掉函数1的注释后,运行结果如下:
call function 1

通过类型提升实现的匹配

image.png
关于类型提升,这里不多做介绍。简单说明类型提升规则:

  • float将提升到double
  • char、short和相应的signed、unsigned类型将提升到int

我们来看一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
//函数1
/*
int f(short a)
{
cout<<"call function 1"<<endl;
return 0;
}*/
//函数2
int f(int a)
{
cout<<"call function 2"<<endl;
return 0;
}
int main()
{
short a = 2;
f(a);
return 0;
}

同样地,我们暂时把函数1注释掉。由于a是short类型,但是通过类型提升,可以转换为int,因为它也能调用函数2。运行结果如下:
call function 2
但去掉函数1注释后,由于精确匹配优于通过类型提升的匹配,因此将会调用函数1,运行结果如下:
call function 1

通过算术类型转换实现的匹配

short int和float,double等之间的转换,都是算术类型之间的转换。我们仍然来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
//函数1
int f(int a)
{
cout<<"call function 1"<<endl;
return 0;
}
//函数2
int f(double a)
{
cout<<"call function 2"<<endl;
return 0;
}
int main()
{
short a = 2;
f(a);
return 0;
}

在这里,short类型的a既可以通过类型提升转换为int,也可以通过算术类型转换成为double。这个时候,哪个才是最佳匹配呢?我们看运行结果:
call function 1
对于这个结果,并不意外,因为前面我们已经说到,通过类型提升的转换是优于算术转换的,因而函数1是它的最佳匹配函数。

二义性示例

前面基本能够找到最佳匹配,我们来看一个有多个可行函数,最后却没有最佳匹配的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
using namespace std;
//函数1
void f(double a,int b)
{
cout<<"function 1"<<endl;
}
//函数2
void f(int a,double b)
{
cout<<"function 2"<<endl;
}
int main()
{
f(1,1);
return 0;
}

函数1和函数2都是可行函数,但它们都没有在任意一个参数上比对方更好,因此将会产生二义性,编译时将会报错:
error: call of overloaded ‘f(int, int)’ is ambiguous

总结

  • 调用重载函数时,应当避免强制类型转换。
  • 设计重载函数时应避免可能产生的二义性。
  • 如果无法找到可行函数,编译器将报错。
  • 设计重载函数的时候,希望避免需要用到上面的知识,而在定位问题时能够利用上面的知识很快定位问题。

Demo

1
2
3
4
5
6
7
8
9
10
void func(short w){
cout<<"function int"<<endl;
};
void func(long w){
cout<<"function long"<<endl;
};
int main() {
int i = 23141592;
func(i); // error Call to 'func' is ambiguous
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

void func(short w){
cout<<"function short"<<endl;
};
void func(long w){
cout<<"function int"<<endl;
};

int main() {
char c = 7; // char->int 优于char->short
short s = 4; // short->int 优于 short->long
int i = 4;
func(i); // int->long or int->short 有二义性
func(s);
func(c); // char->short or char->long 有二义性
}