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

jkj*_*uio 18 c++ operators type-conversion ambiguous

operator bool打破了operator<以下示例中的使用.任何人都可以解释为什么bool仅仅是在为相关if (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;
}
Run Code Online (Sandbox Code Playgroud)

当我编译时,我得到:

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)
Run Code Online (Sandbox Code Playgroud)

das*_*ght 24

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

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

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

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

if (a < Foo(0)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 更准确地说,从"bool"到"int"的转换是*积分促销*而不是*转换*.见(例如)[n4296](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf)第13.3.3.2节[over.ics.rank].是的,它非常复杂.避免隐式转换! (7认同)
  • @jkjyuio Zero不仅是一个`int`,C++将它视为可以转换的任何类型,包括`int`,`bool`,pointers,floats,double,等等.当零被视为`bool`时,它与`false`相同.它不算作转换. (3认同)

Arn*_*gel 13

重点是:

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

  • operator <(const Foo&, const Foo&).使用此重载需要用户定义的文字转换0Foo使用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 *"改进的"更安全的版本,包括指向成员或不完整的私有类型的指针.幸运的是,C++ 11引入了一种更可读,更一致的方法,可以在隐式使用后阻止整数提升operator bool(),即将运算符标记为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所以它会优先于.