重载运算符调用错误函数

7
在这段代码中,为什么 (10 != i) 调用了 == 而不是 !=?而其他两个调用了 !=
#include <iostream>

class Integer
{
  int x;

public:
  bool
  operator== (const Integer &i)
  {
    std::cout << "==";
    return x == i.x;
  }
  bool
  operator!= (const Integer &i)
  {
    std::cout << "!=";
    return x != i.x;
  }
  Integer (int t = 0) { x = t; }
};

int
main ()
{
  Integer i;
  std::cout << (i != i) << '\n';   // calls !=
  std::cout << (i != 100) << '\n'; // calls !=
  std::cout << (10 != i) << '\n';  // calls ==
}

2
你没有为 int != Integer 定义运算符。 - paddy
3
如果没有"!="运算符,编译器可能会尝试使用!(a == b)作为替代方式。这是该语言的最近新增内容。 - BoP
1
添加了C++20标签,因为它只能在该语言版本中编译(尽管我不知道为什么将a!= b更改为!(a == b)会在这种情况下启用隐式转换)。 - Yksisarvinen
1
在C++20中,10 != i将调用由operator==合成的operator!=,而不是您定义的那个。请参见cppinsights - 康桓瑋
在C++20或更高版本中,您不需要定义任何operator!=。只有定义operator==就足以做正确的事情(假设您不想要任何不寻常的!=语义)。只需不声明operator!=即可。 - user17732522
显示剩余2条评论
2个回答

10
在 C++20 之前,你需要添加两个自由函数来进行比较,其中 int 在左侧:
bool operator==(int lhs, const Integer& rhs) {
    return rhs == lhs;
}

bool operator!=(int lhs, const Integer& rhs) {
    return rhs != lhs;
}

您还应该将成员比较运算符设置为const限定符:

class Integer {
public:
    //...
    bool operator==(const Integer &i) const { // note const
        std::cout << "==";
        return x == i.x;
    }
    bool operator!=(const Integer &i) const { // note const
        std::cout << "!=";
        return x != i.x;
    }
    //...
};

您还可以删除成员运算符以简化操作。现在,左侧的 int 将被隐式转换为 Integer,然后与右侧进行比较:

class Integer {
    int x;

public:
    Integer(int t = 0) : x{t} {}

    friend bool operator==(const Integer& lhs, const Integer& rhs) {
        return rhs.x == lhs.x;
    }

    friend bool operator!=(const Integer& lhs, const Integer& rhs) {
        return !(rhs == lhs);
    }    
};

既然您标记了C++20,您可以让operator==处理所有工作。请参见默认比较。它也将用于operator!=

class Integer {
    int x;

public:
    bool operator==(const Integer &i) const {
        std::cout << "==";
        return x == i.x;
    }
    
    Integer(int t = 0) : x{t} {}
};

...并且它将正确显示它使用了 operator== 来进行所有的 != 比较(并对其取反)。

来自于默认值相等比较的更多内容:

一个类可以将 operator== 定义为默认值,并返回 bool。这将生成每个基类和成员子对象的相等比较,按照它们的声明顺序。如果两个对象的基类和成员的值相等,则它们是相等的。如果在声明顺序中先发现了不相等,则测试会短路。

根据 operator== 的规则,这也允许进行不等测试。

这意味着你实际上可以只使用以下代码,因为它仅适用于C++20:

class Integer {
    int x;

public:
    bool operator==(const Integer &i) const = default;
    
    Integer(int t = 0) : x{t} {}
};

甚至更好的是,通过默认设置太空船运算符<=>,可以免费获得所有比较运算符:
class Integer {
    int x;

public:
    auto operator<=>(const Integer &i) const = default;
    
    Integer(int t = 0) : x{t} {}
};

谢谢您的回答,您介意解释一下为什么在 <=> 中使用了 auto 而不是 bool 吗? - Alc
@nvhrw 不用谢!我使用 auto 是因为它的返回类型比较“棘手”。请参见:三路比较 - Ted Lyngmo
2
很抱歉我不能选择这个作为答案,但我已经点赞了。对于那些不关心内部细节的人来说,你的解决方案是有意义的,而另一个答案则深入探讨了我期望得到的细节。 - Alc
3
我认为你在接受那个答案时做得非常正确。不用感到难过! :) - Ted Lyngmo

4

有两个C++20的新加入,使得这成为可能(请注意,在较早的标准版本中您的代码无法编译)。

  1. 如果没有适合这些参数的!=,编译器会尝试用!(a == b)替换a != b

  2. 如果没有适合的a == b,编译器还将尝试b == a

因此发生了什么——编译器首先注意到它不知道如何比较10 != i,因此它尝试!(10 == i)。 仍然找不到合适的比较,所以它尝试!(i == 10),最终可以使用您的隐式构造函数将10转换为Integer
通过添加更多的调试信息可以轻松验证:

  bool
  operator== (const Integer &i) const
  {
    std::cout << x << "==" << i.x << ' ';
    return x == i.x;
  }

在最后一行将打印0==10 1在线查看)。


如评论所指出的,由于C++20编译器的前述行为,您甚至不需要operator !=,编译器会自动将任何此类调用转换为operator ==


1
@StoryTeller-UnslanderMonica,Yksisarvinen的问题是(我认为),为什么不考虑或选择使用operator!=(Integer, Integer)和隐式转换。 - Sebastian
@Sebastian - 我的评论一直回溯到C++98,对此也有同样的解决方案。 - StoryTeller - Unslander Monica
我决定接受这个答案,因为它解释了编译器评估顺序,这正是我想知道的。另一个答案展示了一种不同的简化方法。 - Alc
@Yksisarvinen为什么!(10 == i)不会触发隐式转换?它与!(i == 10)有何不同,为什么顺序在这里很重要。 - Alc
1
这里的措辞(“如果没有合适的”)暗示编译器先尝试X,如果失败了,则尝试Y。实际情况并非如此-编译器同时尝试X和Y,并选择最佳的一个。可能不存在X或Y(在这种情况下无关紧要),但如果两者都存在,则仍然可能存在歧义。 - Barry
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接