以下代码
#include <string>
struct Foo {
operator double() {
return 1;
}
int operator[](std::string x) {
return 1;
}
};
int main() {
Foo()["abcd"];
}
在g++中可以很好地编译,但在clang和intel编译器中无法编译,因为声明的方法和本机操作符[]
之间存在歧义。
如果Foo
有隐式转换到int
,对我来说就很清楚了,但这里的转换是到double
。这不是解决了歧义吗?
§13.3.3.1.2 [over.ics.user]/p1-2:
用户定义的转换序列由初始标准组成转换序列后面跟着用户定义的转换(12.3)然后是第二个标准转换序列。如果用户定义的转换由构造函数(12.3.1)指定,初始的标准转换序列将源类型转换为类型由构造函数的实参要求。如果用户定义的转换由转换函数(12.3.2)指定,即初始的标准转换序列将源类型转换为隐式类型转换函数的对象参数。
第二个标准转换序列转换用户自定义转换为序列的目标类型。
特别地,有一个从浮点型到整型的隐式转换(§4.9 [conf .fpint]/p1):
浮点型的右值可以转换为an型的右值整数类型。转换截断;也就是小数部分就会被丢弃。如果截断的值不能,则行为未定义以目标类型表示。
对于重载解析的目的,适用的候选对象是:
Foo::operator[](std::string x) // overload
operator[](std::ptrdiff_t, const char *); // built-in
给定一个类型为(Foo, const char [5])
的参数列表。
要匹配第一个操作符函数,第一个实参是精确匹配;第二种需要用户定义的转换。
要匹配第二个内置函数,第一个实参需要一个用户定义的转换序列(从用户定义的到double
的转换,然后是到std::ptrdiff_t
的标准转换,一个浮点整型转换)。第二个参数需要进行标准的数组到指针的转换(仍然精确匹配秩),这比用户定义的转换要好。
因此对于第一个参数,第一个函数更好;对于第二个参数,第二个函数更好,我们有一个交叉的情况,重载解析失败,程序是错误的。
注意,出于操作符重载解析的目的,用户定义转换序列可以有两个标准转换序列(一个在用户定义转换之前,一个在用户定义转换之后),非类类型的操作数可以转换为匹配候选操作数,如果选择内置操作符,则第二个标准转换序列不应用于类类型的操作数。在将操作符解释为内置(§13.3.1.2 [over.match.oper]/p7)
之前,对非类类型的操作数不应用任何转换:如果重载解析选择了内置候选项,则类类型的操作数被转换为对应类类型的操作数所选参数的操作功能,除第二项外用户定义的转换序列的标准转换序列(13.3.3.1.2)不适用。然后将操作符视为
因此,如果Foo::operator[](std::string x)
被删除,编译器应该报告一个错误,尽管clang不会。这是一个明显的clang错误,因为它不能拒绝标准中给出的示例。