为什么这个涉及重载运算符和隐式转换的C ++表达式不明确?

问题描述 投票:18回答:4

在下面的例子中,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)
c++ type-conversion operators ambiguous
4个回答
13
投票

重点是:

首先,operator <有两个相关的重载。

  • operator <(const Foo&, const Foo&)。使用此重载需要使用0将文字Foo用户定​​义的转换为Foo(int)
  • operator <(int, int)。使用此重载需要使用用户定义的Foobool转换为operator bool(),然后升级到int(这是标准,与转换不同,正如Bo Persson所指出的那样)。

这里的问题是:模糊性从何而来?当然,第一次呼叫只需要用户定义的转换,比第二次呼叫更明智,这需要用户定义的转换,然后是促销?

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

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

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

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

正如其他人所提到的,你也可以将Foo(int)构造函数标记为显式。这将具有消除operator <(const Foo&, const Foo&)过载的相反效果。

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

  • operator <(int, const Foo&)
  • operator <(const Foo&, int)

在这个例子中,后者将优先于上述重载作为完全匹配,即使你没有引入explicit。同样的事情,例如对于

  • operator <(const Foo&, long long)

这将优于operator <(const Foo&, const Foo&)a < 0,因为它的使用只需要促销。


24
投票

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

  • a转换为bool,并将结果与​​0与内置运算符<进行比较(一次转换)
  • 0转换为Foo,并将结果与​​你定义的<进行比较(一次转换)

这两种方法都等同于编译器,因此它会发出错误。

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

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

4
投票

因为编译器不能在bool operator <(const Foo &,const Foo &)operator<(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<(int, int)作为候选人,虽然“不允许多步转换”。

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

§12.3.4[class.conv]

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

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


2
投票

这正是编译器告诉你的。

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.