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++将这两个操作符定义为两个不同的函数呢?


当前回答

我只回答你问题的第二部分,即:

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

允许开发人员重载两者是有意义的一个原因是性能。您可以通过同时实现==和!=来允许优化。那么x != y可能比!(x == y)便宜。一些编译器可能能够为您优化它,但也可能不能,特别是当您有涉及大量分支的复杂对象时。

即使在Haskell中,开发人员非常重视法律和数学概念,仍然允许重载==和/=,正如您可以在这里看到的(http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html#v:-61--61-):

$ ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
λ> :i Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
        -- Defined in `GHC.Classes'

这可能被认为是微观优化,但在某些情况下可能是合理的。

其他回答

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

你不必同时定义两者。 如果它们是互斥的,您仍然可以通过在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 );
}

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

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

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

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

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

当a == b返回bool类型以外的值时,您不会希望语言自动将a != b重写为!(a == b)。有几个原因可以让它这样做。

您可能有表达式构建器对象,其中a == b不会也不打算执行任何比较,而只是构建一些表示a == b的表达式节点。

你可能有惰性求值,其中a == b不会也不打算直接执行任何比较,而是返回某种惰性<bool>,可以在以后的某个时间隐式或显式地转换为bool来实际执行比较。可能与表达式构建器对象结合使用,以便在求值之前完成表达式优化。

你可能有一些自定义的可选<T>模板类,其中给定可选变量T和u,你想允许T == u,但让它返回可选<bool>。

可能还有我没想到的事。即使在这些例子中,操作a == b和a != b都是有意义的,但a != b与!(a == b)不是一回事,因此需要单独的定义。

有一些非常完善的约定,其中(a == b)和(a != b)都是假的,不一定是相反的。特别地,在SQL中,任何与NULL的比较都会产生NULL,而不是true或false。

如果可能的话,创建这样的新示例可能不是一个好主意,因为这太不直观了,但是如果您试图对现有的约定建模,那么可以选择使您的操作符在该上下文中表现得“正确”。

权力越大,责任越大,或者至少是非常好的风格指南。

==和!=可以重载来做任何你想做的事情。这是福也是祸。不能保证!=意味着!(a==b)。