【问题标题】:Should this compile? Overload resolution and implicit conversions这应该编译吗?重载分辨率和隐式转换
【发布时间】:2012-02-13 11:00:36
【问题描述】:

这个例子似乎可以用 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 也可以正常编译。

编辑:(澄清问题)

这似乎是重载解决方案中的一个复杂领域,因此,我非常感谢在这种情况下详细解释重载解决方案并引用标准的答案(如果有一些晦涩/先进的可能是未知规则)。

如果 clang 是正确的,我也很想知道为什么这两者模棱两可/一个不优于另一个。答案可能必须解释重载解决方案如何考虑两个候选对象所涉及的隐式转换(用户定义和标准转换),以及为什么一个不比另一个更好。

注意:如果将 operator double() 改为 operator bool(),这三个 (clang, vc, gcc) 都会拒绝编译,并出现类似的模棱两可的错误。

【问题讨论】:

  • 澄清(可能的)歧义来自何处:a[b] 等价于b[a],当operator[] 未重载时。
  • 对,我正准备在几分钟前添加。会的,谢谢。
  • 既然你提到你的 GCC 很旧,我在尚未发布的 4.7 上进行了尝试。它的作用和你的一样:编译原件就好了,用operator bool出错。
  • 感谢您试用。将进行编辑以反映您的努力。
  • @MikeSeymour: a[b] 在这种情况下不等于b[a](因为在这种情况下a 不是const char* 或可转换为它)。你说的是这种情况:char a[100]; int b; 然后a[b] 相当于b[a]

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


【解决方案1】:

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

§13.5.5 [over.sub]

因此,如果 T::operator[](T1) 存在并且 如果运算符被重载解决机制 (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-th 参数转换为可行函数Fi-th 参数的类型的隐式转换序列。 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(续)鉴于这些定义,一个可行的函数F1被定义为比另一个可行的函数F2更好的函数如果对于所有参数iICSi(F1)并不差转换顺序比ICSi(F2),然后[...]

p2 如果恰好有一个可行函数比所有其他可行函数更好,那么它就是重载决议选择的那个;否则调用格式不正确。

好吧,他妈的。 :) 因此,Clang 拒绝该代码是正确的。

【讨论】:

    【解决方案2】:

    似乎毫无疑问,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*)
    • 对于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) 更差的转换序列,...

    因此,所考虑的两个候选函数中没有一个比另一个更好,因此调用是非良构的。 IE。 Clang 似乎是正确的,代码不应该编译。

    【讨论】:

      【解决方案3】:

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

      13.6 内置运算符 [过度构建]

      代表第 5 条中定义的内置运算符的候选运算符函数在 本条。这些候选函数参与运算符重载解决过程,如所述 在 13.3.1.2 中并且不用于其他目的。 [注意:因为内置运算符只接受操作数 非类类型,并且仅当操作数表达式最初具有类时才会发生运算符重载决议 或枚举类型,运算符重载决议只能在操作数时解析为内置运算符 具有一个类类型,该类类型具有用户定义的转换为适合运算符的非类类型,或者当 操作数具有枚举类型,可以转换为适合运算符的类型。另请注意 本小节中给出的一些候选运算符函数比内置的更宽松 运营商自己。如 13.3.1.2 中所述,在通过重载决议选择内置运算符之后 该表达式服从第 5 章中给出的内置运算符的要求,因此 那里给出的任何其他语义约束。如果有同名用户写的候选人 和参数类型作为内置候选算子函数,内置算子函数被隐藏并且 不包含在候选函数集中。 ——尾注]

      对于每个 cv-qualified 或 cv-unqualified object type T 都存在以下形式的候选运算符函数

      T& 运算符[](T *, std::ptrdiff_t);

      T& 运算符[](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)]; }
      

      给某事上课

      【讨论】:

      • 我不明白为什么在 C 中拥有 T& operator[](std::ptrdiff_t, T *); 很重要。
      • 如果我理解正确的话。这解释了为什么要考虑内置运算符。但我也很感兴趣,为什么铿锵认为这两者是模棱两可的,而不是更喜欢一个。我的问题中没有这么说。我会添加它。
      • 据我们所知,gcc 和 VC10 也可能在重载解析期间考虑了内置运算符,但认为它没有歧义并选择调用 Something::operator[] ,因此编译成功,而 clang 认为这两者是模棱两可的(不喜欢一个胜过另一个)。
      • @Neil G:C 根本没有 operator[] -- a[b]*(a+b) 的语法简写,甚至在考虑类型之前就转换为后者。我猜你的问题相当于“为什么允许 int+pointer 和 pointer+int”,我想这更容易,因为+ 通常是可交换的
      • @ChrisDodd:好的。我认为应该修复 C 和 C++,以便 a[b] 始终是 a[b] 运算符,并且该运算符不应该存在,因为 a 是数字类型。
      猜你喜欢
      • 2016-04-02
      • 1970-01-01
      • 1970-01-01
      • 2018-08-20
      • 2011-07-17
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多