【问题标题】:Why is this C++ expression involving overloaded operators and implicit conversions ambiguous?为什么这个涉及重载运算符和隐式转换的 C++ 表达式不明确?
【发布时间】:2018-02-05 15:25:41
【问题描述】:

operator bool 在以下示例中打破了operator< 的使用。谁能解释为什么boolif (a < 0) 表达式中与特定运算符一样相关,是否有解决方法?

struct Foo {
    Foo() {}
    Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b) {
        return true;
    }
};

int main() {
    Foo a, b;
    if (a < 0) {
        a = 0;
    }
    return 1;
}

当我编译时,我得到:

g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
     if (a < 0) {
           ^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
     friend bool operator<(const Foo& a, const Foo& b)

【问题讨论】:

  • 0int 类型,而不是 Foo 类型
  • 你真的需要这两种隐式转换吗?单一的隐式转换可能会让人头疼,但两种方式的隐式转换肯定会引发偏头痛。
  • 制作单参数构造函数explicit
  • 顺便说一句:您还没有为a = 0; 定义赋值运算符,这可能会导致另一个错误消息...
  • 这是问题的简化示例。

标签: c++ type-conversion operators ambiguous


【解决方案1】:

这里的问题是C++有两个选项来处理a &lt; 0表达式:

  • a转换为bool,并使用内置运算符&lt;将结果与0进行比较(一次转换)
  • 0 转换为Foo,并将结果与​​您定义的&lt; 进行比较(一次转换)

这两种方法都等价于编译器,所以会报错。

您可以通过删除第二种情况的转换来明确这一点:

if (a < Foo(0)) {
    ...
}

【讨论】:

  • a转换成bool,是的,但是boolint不一样,
  • @jkjyuio 零不仅是int,C++ 将其视为任何可以转换的类型,包括intbool、指针、浮点数、双精度数等.当零被视为bool 时,它与false 相同。这不算作一次转化。
  • 更准确地说,从boolint 的转换是整体提升,而不是转换。请参阅(例如)n4296 中的第 13.3.3.2 [over.ics.rank] 节。是的,这非常复杂。避免隐式转换!
【解决方案2】:

这正是编译器告诉你的。

为编译器解决if (a &lt; 0) 的一种方法是使用您提供的Foo(int x) 构造函数从0 创建对象。

第二种是使用operator bool 转换并将其与int(促销)进行比较。您可以在Numeric promotions 部分阅读更多相关信息。

因此,它对于编译器来说是模棱两可的,它无法决定你想要它走哪条路。

【讨论】:

  • 为什么比较 boolint 不计为额外转化?
  • @jkj - boolint 是“促销”而不是转换。 :-) 请注意,“标准转换”一章在标准文档中需要 7 页来解释。完整的解释不适合 StackOverflow。您的基本问题是您的课程提供与课程之间的隐式转换。大多数人避免这样做是因为您注意到的确切原因 - 这会让读者和编译器感到困惑。
  • @Bo Persson,感谢您指出这一点。 Arne Vogel 在上面已经详细解释过了。
【解决方案3】:

因为编译器无法在bool operator &lt;(const Foo &amp;,const Foo &amp;)operator&lt;(bool, int) 之间进行选择,这两者都适合这种情况。

为了解决这个问题,制作第二个构造函数explicit:

struct Foo
{
    Foo() {}
    explicit Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b)
    {
        return true;
    }
};

编辑: 好的,我终于明白了这个问题的真正意义 :) OP 询问为什么他的编译器提供 operator&lt;(int, int) 作为候选者,尽管 “不允许多步转换”

答案: 是的,为了调用operator&lt;(int, int) 对象a 需要转换Foo -&gt; bool -&gt; int但是,C++ 标准实际上并没有说“多步转换是非法的”。

§ 12.3.4 [class.conv]

最多一次用户定义的转换(构造函数或转换 function) 隐式应用于单个值。

boolint 不是用户定义的 转换,因此它是合法的,编译器完全有权选择operator&lt;(int, int) 作为候选。

【讨论】:

  • 否,因为编译器没有列出 operator
  • 在你的情况下,operator&lt;(int, int)。但是,MSVC 2017 确实列出了operator&lt;(bool,int)
  • 这是否意味着将bool 转换为int 不算作转换步骤。所以它们是相同 类型?
  • 我不确定转换步骤,但 intbool 在标准 C++ 中绝对是不同的类型(尽管在 C 和可能在标准 C++ 之前没有 bool,仅限int)
【解决方案4】:

重点是:

首先,operator &lt; 有两个相关的重载。

  • operator &lt;(const Foo&amp;, const Foo&amp;)。使用此重载需要使用 Foo(int) 将文字 0 用户定义转换为 Foo
  • operator &lt;(int, int)。使用此重载需要使用用户定义的operator bool()Foo 转换为bool,然后将promotion 转换为int(在标准术语中,这与转换不同,如Bo Persson 指出)。

这里的问题是:歧义从何而来?当然,第一次调用,只需要自定义转换,比第二次调用更明智,需要自定义转换,然后进行促销?

但事实并非如此。该标准为每个候选人分配一个等级。但是,“用户定义的转换后促销”没有排名。这与仅使用用户定义的转换具有相同的等级。简单(但非正式地)说,排名顺序看起来有点像这样:

  1. 完全匹配
  2. (仅)需要促销
  3. (仅)需要隐式转换(包括从 C 继承的“不安全”转换,例如 floatint
  4. 需要用户定义的转换

(免责声明:如前所述,这是非正式的。当涉及多个参数时,它会变得更加复杂,而且我也没有提到引用或 cv-qualification。这只是一个粗略的概述。)

因此,希望这可以解释为什么调用是模棱两可的。现在是如何解决这个问题的实际部分。 几乎从来没有提供operator bool() 的人希望它被隐式地用于涉及整数算术或比较的表达式中。在 C++98 中,有一些模糊的解决方法,从 std::basic_ios&lt;CharT, Traits&gt;::operator void * 到“改进的”更安全的版本,包括指向成员的指针或不完整的私有类型。幸运的是,C++11 在隐式使用operator bool() 后引入了一种更易读和更一致的防止整数提升的方法,即将运算符标记为explicit。这将完全删除 operator &lt;(int, int) 重载,而不仅仅是“降级”它。

正如其他人所提到的,您还可以将Foo(int) 构造函数标记为显式。这将产生相反的效果,即移除 operator &lt;(const Foo&amp;, const Foo&amp;) 重载。

第三种解决方案是提供额外的重载,例如:

  • operator &lt;(int, const Foo&amp;)
  • operator &lt;(const Foo&amp;, int)

在本例中,后者将优先于上述重载作为精确匹配,即使您没有引入explicit。同样的道理,例如对于

  • operator &lt;(const Foo&amp;, long long)

这比a &lt; 0 中的operator &lt;(const Foo&amp;, const Foo&amp;) 更受欢迎,因为它的使用只需要升级。

【讨论】:

  • 非常感谢您的详细回答。我将此标记为正确的。是的,我的问题是关于比赛的排名,因为 bool->int 似乎是一个附加转换。但它是用促销来解释的。此外,确实拥有explicit operator bool 的修复程序,您几乎永远不会想要进一步的提升。也感谢其他人的意见。
猜你喜欢
  • 2010-12-09
  • 2014-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 2010-10-27
相关资源
最近更新 更多