I'm learning about operator overloading in C++, and I see that == and != are simply some special functions which can be customized for user-defined types. My concern is, though, why are there two separate definitions needed? I thought that if a == b is true, then a != b is automatically false, and vice versa, and there is no other possibility, because, by definition, a != b is !(a == b). And I couldn't imagine any situation in which this wasn't true. But perhaps my imagination is limited or I am ignorant of something?

我知道我可以用一个定义另一个,但这不是我要问的。我也没有问通过值和单位来比较对象之间的区别。或者两个对象是否可以同时相等和不相等(这绝对不是一个选项!这些事情是相互排斥的)。我想问的是:

有没有可能在这种情况下,问两个对象是否相等的问题是有意义的,但问它们不相等的问题是没有意义的?(无论是从用户的角度,还是从实现者的角度)

如果没有这种可能性,那么究竟为什么c++将这两个操作符定义为两个不同的函数呢?


当前回答

如果==和!=操作符实际上并不意味着相等,就像<<和>>流操作符并不意味着位移位一样。如果你把这些符号当作其他概念来对待,它们就不必相互排斥。

就相等而言,如果您的用例保证将对象视为不可比较的,那么每次比较都应该返回false(或者如果操作符返回非bool类型,则返回不可比较的结果类型),那么这样做是有意义的。我想不出一个具体的情况,这将是必要的,但我可以看到它是足够合理的。

其他回答

enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

BoolPlus operator==(File& other);
BoolPlus operator!=(File& other);

我不能证明这个操作符重载是正确的,但在上面的例子中,不可能定义操作符!作为运算符==的“对立面”。

但我担心的是,为什么需要两个不同的定义呢?

你不必同时定义两者。 如果它们是互斥的,您仍然可以通过在std::rel_ops旁边定义==和<来保持简洁

Fom cppreference:

#include <iostream>
#include <utility>

struct Foo {
    int n;
};

bool operator==(const Foo& lhs, const Foo& rhs)
{
    return lhs.n == rhs.n;
}

bool operator<(const Foo& lhs, const Foo& rhs)
{
    return lhs.n < rhs.n;
}

int main()
{
    Foo f1 = {1};
    Foo f2 = {2};
    using namespace std::rel_ops;

    //all work as you would expect
    std::cout << "not equal:     : " << (f1 != f2) << '\n';
    std::cout << "greater:       : " << (f1 > f2) << '\n';
    std::cout << "less equal:    : " << (f1 <= f2) << '\n';
    std::cout << "greater equal: : " << (f1 >= f2) << '\n';
}

有没有可能的情况是问关于二的问题 对象相等是有意义的,但是问它们不相等 相等没有意义吗?

我们经常把这些运算符和相等联系起来。 尽管这是它们在基本类型上的行为方式,但没有义务在自定义数据类型上也是如此。 如果你不想,你甚至不需要返回bool值。

我见过人们以奇怪的方式重载操作符,最后却发现这对特定领域的应用程序是有意义的。即使接口显示它们是互斥的,作者也可能希望添加特定的内部逻辑。

(无论是从用户的角度,还是从实现者的角度)

我知道你想要一个具体的例子, 下面是Catch测试框架中我认为比较实用的一个:

template<typename RhsT>
ResultBuilder& operator == ( RhsT const& rhs ) {
    return captureExpression<Internal::IsEqualTo>( rhs );
}

template<typename RhsT>
ResultBuilder& operator != ( RhsT const& rhs ) {
    return captureExpression<Internal::IsNotEqualTo>( rhs );
}

这些操作符做不同的事情,将一个方法定义为另一个方法的!(不是)是没有意义的。这样做的原因是,框架可以打印出所做的比较。为了做到这一点,它需要捕获所使用的重载操作符的上下文。

通过自定义操作符的行为,您可以使它们按照您的要求进行操作。

你可能希望自定义一些东西。例如,您可能希望自定义一个类。可以通过检查特定属性来比较该类的对象。了解了这种情况后,您可以编写一些只检查最小值的特定代码,而不是检查整个对象中每个属性的每一位。

Imagine a case where you can figure out that something is different just as fast, if not faster, than you can find out something is the same. Granted, once you figure out whether something is the same or different, then you can know the opposite simply by flipping a bit. However, flipping that bit is an extra operation. In some cases, when code gets re-executed a lot, saving one operation (multiplied by many times) can have an overall speed increase. (For instance, if you save one operation per pixel of a megapixel screen, then you've just saved a million operations. Multiplied by 60 screens per second, and you save even more operations.)

Hvd的回答提供了一些额外的例子。

如果没有这种可能性,那么究竟为什么c++将这两个操作符定义为两个不同的函数呢?

因为你可以让它们超载,通过超载,你可以赋予它们一个与原来完全不同的含义。

例如,操作符<<,最初是位左移操作符,现在通常重载为插入操作符,如std::cout << something;和原来的意思完全不同。

因此,如果您接受重载操作符时操作符的含义会发生变化,那么就没有理由阻止user给operator ==赋予一个与operator !=不完全相反的含义,尽管这可能会令人困惑。

有没有可能的情况是问关于二的问题 对象相等是有意义的,但是问它们不相等 相等没有意义吗?(无论是从用户的角度,还是从 实现者的角度)

这只是个人观点。也许不是。但是语言的设计者并不是无所不知的,他们决定不限制那些可能想出有意义的情况的人(至少对他们来说)。