一天早上遇到的事物,下午又恰好看到了专业的补充,对此将其记录下来。
二义性调用(Ambiguous Call),属于一种编译错误。
在调用函数,去匹配合适的参数列表时,发现当前所提供参数,能匹配出多个重载函数。比如:
int add(int a, int b) {
return a + b;
}
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
add(10, 20);
add(10.5, 20.5);
add(string("10.5"), string("20.5"));
return 0;
}
当然,编译器不会那么呆,会追求最佳匹配。当参数为两个 int
时,就会调用 add(int, int)
, 而不会是 add(T, T)
。
但是,若最佳匹配也存在多个的情况下,那么就不对劲了:
long long add(int, long long) {
return 0;
}
long long add(long long, int) {
return 0;
}
int main() {
// ???
add(10, 20);
return 0;
}
对于字面量整数来说,完全兼容 int
与 long long
类型,于是就无法区分,到底要调用是哪一个函数,因为无论调用哪一个,都合情合理,从类型的角度来说完全没有问题。
对于
add(int, long long)
与add(long long, int)
所形成的的重构有点扯,但此处只是演示二义性问题,不探究严谨性。
最佳解决方案是:完全杜绝这种函数重构的产生。
否则,请在调用时,显式告诉编译器,该字面量值的类型具体是什么,而不要让其自行推测。比如:
long long add(int, long long) {
return 0;
}
long long add(long long, int) {
return 0;
}
int main() {
// ok
int a = 10;
long long b = 20;
add(a, b);
add((long long)10, 20);
add(10, (long long)20);
return 0;
}
其中发现,若是只标注任意一参数类型为 int
,依然会导致二义性调用,比较有趣,可以分析一下为什么。
int main() {
// ???
add((int)10, 20);
add(10, (int)20);
return 0;
}
若是显式说明其中一个参数为 int
,那么根据我们的观察,应该去选择函数签名为 int a, T b
或 T a, int b
的函数,来达成匹配。
然而对于整数字面量来说,一个默认行为就是,将其当作 int
进行处理,于是又回到了原点,int
都能无损转换为 long long
,那谁来转换。显式标注并不能参与转换优先级竞争。
只有当另一个未显式指定 int
类型的参数,其数值超过了 int
范围,那么才会将其定位至 long long
, 从而达成匹配。
正常情况下,是很难遇到二义性调用的,想要随手写出这种函数都很难,知道这样一个术语,用于描述这种状况即可。
End