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

时间:2012-01-18 17:59:46

标签: c++ visual-c++ clang implicit-conversion overload-resolution

这个例子似乎是用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;
}

但是clang抱怨道:

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])(在用户定义的转换和标准转换为double到long之后)。

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

编辑:(澄清问题)

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

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

注意:如果operator double()更改为operator bool(),则所有三个(clang,vc,gcc)都将拒绝使用类似的模糊错误进行编译。

3 个答案:

答案 0 :(得分:12)

通过逐步完成重载解决方案,应该更容易理解为什么重载决策是不明确的。

§13.5.5 [over.sub]

  

因此,对于类型为x[y]的类对象x.operator[](y),如果x存在,则下标表达式T将被解释为T::operator[](T1),如果存在,则为{strong>通过重载解析机制(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是静态成员函数,[...];否则,
  •   
  • let ICSi(F)表示隐式转换序列,它将列表中的i - 参数转换为可行函数i的{​​{1}} - 参数的类型。 13.3.3.1定义了隐式转换序列,13.3.3.2定义了一个隐式转换序列比另一个转换序列更好的转换序列或更差转换序列的含义。
  •   

现在让我们在重载集(F)中为f1f2构建隐式转换序列:

§13.3.3.1

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.1.1)是比用户定义的转换序列或省略号转换序列更好的转换序列。

因此§13.3.3.2 [over.ics.rank] p2优于ICS1(f1)ICS1(f2)ICS2(f1)更差。 相反,ICS2(f2)ICS1(f2)更差,ICS1(f1)优于ICS2(f2)

ICS2(f1)

  

p1(续)鉴于这些定义,可行函数§13.3.3 [over.match.best]被定义为比另一个可行函数F1 更好的函数,如果对于所有参数F2,{ {1}}转换序列不比i 差,然后[...]

     

p2如果只有一个可行的函数比所有其他可行函数更好的函数,则它是由重载决策选择的函数;否则电话会形成不良。

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

答案 1 :(得分:4)

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

  • 代表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似乎是正确的,代码不应该编译。

答案 2 :(得分:3)

从C ++ 11规范中的13.6开始,clang在这里是正确的:

  

<强> 13.6   内置运营商   [over.built]

     

表示第5章中定义的内置运算符的候选运算符函数在中指定        这个子条款。这些候选函数参与所述的运算符重载解析过程       在13.3.1.2中,不得用于其他目的。 [注意:因为内置运算符只使用操作数      非类型类型,并且仅当操作数表达式最初具有类时才会发生运算符重载决策     或枚举类型,运算符重载决策只能在操作数时解析为内置运算符    有一个类类型,它具有用户定义的转换为适合运算符的非类类型,或何时转换     操作数具有枚举类型,可以将其转换为适合运算符的类型。另请注意    本子条款中给出的一些候选运算符函数比内置函数更宽松   经营者自己。如13.3.1.2中所述,在通过重载决策选择内置运算符之后   表达式受第5章中给出的内置运算符的要求的约束,因此也符合   在那里给出任何额外的语义约束。如果有一个用户写的候选人具有相同的名称   和参数类型作为内置候选运算符函数,隐藏内置运算符函数   不包括在候选函数集中。 - 结束说明]

     
    
      
        

      
    
  
     

对于每个cv-qualified或cv-nonqualified对象类型T,都存在表单的候选运算符函数

     
    

T&安培; operator [](T *,std :: ptrdiff_t);

         

T&安培; operator [](std :: ptrdiff_t,T *);

  

修改

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

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

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

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

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

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

to class Something