首页 文章

这会编译吗?重载分辨率和隐式转换

提问于
浏览
18

这个例子似乎用VC10和gcc编译(虽然我的gcc版本很老) .

编辑:R . Martinho Fernandez在gcc 4.7上试过这个并且行为仍然是一样的 .

struct Base
{
    operator double() const { return 0.0; }
};

struct foo
{
    foo(const char* c) {}
};

struct Something : public Base
{
    void operator[](const foo& f) {}
};

int main()
{
    Something d;
    d["32"];

    return 0;
}

但克朗抱怨道:

test4.cpp:19:6: error: use of overloaded operator '[]' is ambiguous (with operand types 'Something' and 'const char [3]')
    d["32"]
    ~^~~~~
test4.cpp:13:10: note: candidate function
    void operator[](const foo& f) {}
         ^
test4.cpp:19:6: note: built-in candidate operator[](long, const char *)
    d["32"]
     ^
test4.cpp:19:6: note: built-in candidate operator[](long, const restrict char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile restrict char *)

重载决议考虑了这个表达式,考虑了两个可能的函数:

  • 调用Something :: operator [](在用户定义的转换之后)

  • 为const char *调用内置运算符(想想"32" [d])(在用户定义转换和标准转换后加倍到long) .

如果我写了 d["32"]d.operator[]("32") ,那么重载解析甚至不会查看选项2,而clang也可以正常编译 .

编辑:(澄清问题)

这似乎是重载决策中的一个复杂区域,因此我非常感谢在这种情况下详细解释过载分辨率的答案,并引用标准(如果有一些模糊/高级可能是未知规则) .

如果铿锵是正确的,我也有兴趣知道为什么这两个是模棱两可的/一个不优于另一个 . 答案可能需要解释重载决策如何考虑两个候选者所涉及的隐式转换(用户定义和标准转换)以及为什么一个不比另一个好 .

注意:如果operator double()更改为operator bool(),则所有三个(clang,vc,gcc)将拒绝编译时出现类似的模糊错误 .

3 回答

  • 12

    通过逐步完成重载分辨率,应该更容易理解为什么重载决策是模糊的 .

    §13.5.5 [over.sub]

    因此,如果T :: operator [](T1)存在且如果选择运算符作为最佳匹配,则对于类型为T的类对象x,将下标表达式x [y]解释为x.operator [](y)由重载解析机制(13.3.3)起作用 .

    现在,我们首先需要一个重载集 . 这是根据 §13.3.1 构建的,包含成员以及非成员函数 . 有关更详细的说明,请参见this answer of mine .

    §13.3.1 [over.match.funcs]

    p2候选函数集可以包含要针对同一参数列表解析的成员函数和非成员函数 . 因此,参数和参数列表在此异构集中是可比较的,成员函数被认为具有额外的参数,称为隐式对象参数,其表示已为其调用成员函数的对象 . [...] p3同样,在适当的情况下,上下文可以构造一个参数列表,其中包含一个隐含的对象参数来表示要操作的对象 .

    // abstract overload set (return types omitted since irrelevant)
    f1(Something&, foo const&); // linked to Something::operator[](foo const&)
    f2(std::ptrdiff_t, char const*); // linked to operator[](std::ptrdiff_t, char const*)
    f3(char const*, std::ptrdiff_t); // linked to operator[](char const*, std::ptrdiff_t)
    

    然后,构造一个参数列表:

    // abstract argument list
    (Something&, char const[3]) // 'Something&' is the implied object argument
    

    然后针对重载集的每个成员测试参数列表:

    f1 -> identity match on argument 1, conversion required for argument 2
    f2 -> conversion required for argument 1, conversion required for argument 2 (decay)
    f3 -> argument 1 incompatible, argument 2 incompatible, discarded
    

    然后,由于我们发现需要隐式转换,我们来看看 §13.3.3 [over.match.best] p1

    如下定义ICSi(F):如果F是静态成员函数,[...];否则,让ICSi(F)表示隐式转换序列,它将列表中的第i个参数转换为可行函数F的第i个参数的类型.13.3.3.1定义了隐式转换序列,13.3.3.2定义了什么这意味着一个隐式转换序列比另一个更好的转换序列或更差的转换序列 .

    现在让我们在重载集( §13.3.3.1 )中构造 f1f2 的隐式转换序列:

    ICS1(f1): 'Something&' -> 'Someting&', standard conversion sequence
    ICS2(f1): 'char const[3]' -> 'foo const&', user-defined conversion sequence
    ICS1(f2): 'Something&' -> 'std::ptrdiff_t', user-defined conversion sequence
    ICS2(f2): 'char const[3]' -> 'char const*', standard conversion sequence
    

    §13.3.3.2 [over.ics.rank] p2

    标准转换序列(13.3.3.1.1)是比用户定义的转换序列或省略号转换序列更好的转换序列 .

    因此 ICS1(f1) 优于 ICS1(f2)ICS2(f1)ICS2(f2) 更差 .
    相反, ICS1(f2)ICS1(f1) 差, ICS2(f2) 优于 ICS2(f1) .

    §13.3.3 [over.match.best]

    p1(续)鉴于这些定义,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,则可行函数F1被定义为比另一个可行函数F2更好的函数,然后[...] p2如果只有一个可行函数比所有其他可行函数更好的函数,则它是由重载决策选择的函数;否则电话会形成不良 .

    好吧,f * ck . :)因此,Clang拒绝该代码是正确的 .

  • 3

    似乎毫无疑问, Something::operator[](const foo& f) 和内置的 operator[](long, const char *) 都是可行的候选函数(13.3.2),用于重载决策 . 真实参数的类型是 Somethingconst char* ,以及我认为的隐式转换序列(ICF):

    • for Something::operator[](const foo& f) :(1-1)身份转换,(1-2) foo("32")foo::foo(const char*) ;

    • for operator[](long, const char *) :(2-1) long(double(d))Something::operator double() const (继承自Base),以及(2-2)身份转换 .

    现在,如果我们根据(13.3.3.2)对这些ICF进行排名,我们可以看出(1-1)是比(2-1)更好的转换,而(1-2)是比(2-2)更差的转换 . 根据(13.3.3)的定义,

    一个可行的函数F1被定义为比另一个可行函数F2更好的函数,如果对于所有参数i,ICSi(F1)不是比ICSi(F2)更差的转换序列,......

    因此,所考虑的两个候选函数都不比另一个好,因此呼叫是不正确的 . 即Clang似乎是正确的,代码不应该编译 .

  • 4

    从C11规范中的13.6可以看出,clang在这里是正确的:

    13.6内置运算符[over.built]本子条款中规定了表示第5章中定义的内置运算符的候选运算符函数 . 这些候选函数参与13.3.1.2中描述的运算符重载解析过程,并且不用于其他目的 . [注意:因为内置运算符只接受具有非类型类型的操作数,并且仅当操作数表达式最初具有类或枚举类型时才会发生运算符重载解析,因此只有当操作数具有操作数时,运算符重载解析才能解析为内置运算符一种类型,具有用户定义的转换为适合于运算符的非类型类型,或者当操作数具有可以转换为适合运算符的类型的枚举类型时 . 另请注意,本子条款中给出的一些候选运算符函数比内置运算符本身更宽松 . 如13.3.1.2中所述,在通过重载解析选择内置运算符之后,表达式受第5章中给出的内置运算符的要求的约束,因此符合那里给出的任何其他语义约束 . 如果存在具有与内置候选运算符函数相同的名称和参数类型的用户编写候选,则内置运算符函数被隐藏并且不包括在候选函数集中 . - 结束注释]:对于每个cv限定或cv非限定对象类型T,存在T&operator []形式的候选运算符函数(T *,std :: ptrdiff_t); T&operator [](std :: ptrdiff_t,T *);

    edit

    一旦你了解了哪些运算符函数存在,这只是标准的重载分辨率,如标准的第13.3节所述 - 大约10页的细节,但它的要点是函数调用不是模糊的,需要是一个单独的函数,至少与每个参数上所有可能的,可行的函数一样好,并且在至少一个参数上比其他函数更好地匹配 . 关于“更好”意味着什么,有很多规范细节,但它归结为(在这种情况下)不需要任何用户定义的转换运算符或对象构造函数的匹配比具有更好的匹配 .

    所以在这种情况下,有两个可行的匹配:

    void Something::operator[](const foo& f)
    operator[](long, const char *)
    

    第一个是第一个参数的更好匹配,而第二个是第二个参数的更好匹配 . 因此,除非有一些其他功能比这两个功能更好,否则其含糊不清 .

    后一点是一个可能的解决方法 - 添加:

    void operator[](const char *a) { return (*this)[foo(a)]; }
    

    上课的东西

相关问题