关于c ++:是= =和!=相互依赖?

Are == and != mutually dependent?

我正在学习C++中运算符重载,并且我看到EDCOX1 0和EDCOX1 1是简单的,可以为用户定义的类型定制的特殊功能。不过,我担心的是,为什么需要两个单独的定义?我认为,如果a == b是真的,那么a != b自动是假的,反之亦然,并且没有其他可能性,因为根据定义,a != b!(a == b)。我无法想象在任何情况下这是不真实的。但也许我的想象力有限,或者我对什么一无所知?

我知道我可以用另一个定义一个,但这不是我要问的。我也不想问按价值或身份比较对象之间的区别。或者两个对象是否可以同时相等和不相等(这绝对不是一个选项!这些东西是相互排斥的)。我想问的是:

在任何情况下,询问两个物体相等是否有意义,但询问两个物体不相等是否有意义?(无论是从用户的角度,还是从实施者的角度)

如果没有这样的可能性,那么为什么地球上C++将这两个操作符定义为两个不同的函数?


a == b返回的不是bool时,您不希望该语言自动将a != b重写为!(a == b)。你为什么要这么做,有几个原因。

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

您可能会有懒惰的评估,其中a == b不直接执行任何比较,也不打算直接执行任何比较,而是返回某种类型的lazy,可以在以后某个时间隐式或显式转换为bool,以实际执行比较。可能与表达式生成器对象结合在一起,以便在计算之前进行完整的表达式优化。

您可能有一些自定义的optional模板类,在给定可选变量tu的情况下,您希望允许t == u但使其返回optional

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


If there is no such possibility, then why on Earth does C++ have these two operators being defined as two distinct functions?

因为你可以让他们超载,通过超载他们,你可以给他们一个完全不同于他们原来的意义。

例如,操作符<<,原来是位左移位操作符,现在通常作为插入操作符重载,就像在std::cout << something中一样;与原来的操作符完全不同。

因此,如果您接受当您重载某个操作符时它的含义会发生变化,那么就没有理由阻止用户向操作符==赋予一个含义,这并不完全是对操作符!=的否定,尽管这可能会令人困惑。


My concern is, though, why are there two separate definitions needed?

你不必定义两者。如果它们是互斥的,那么您仍然可以通过在std::rel-ops旁边定义==<来进行简洁的定义。

FOM CPP参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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) << '
'
;
    std::cout <<"greater:       :" << (f1 > f2) << '
'
;
    std::cout <<"less equal:    :" << (f1 <= f2) << '
'
;
    std::cout <<"greater equal: :" << (f1 >= f2) << '
'
;
}

Is there any situation possible in which asking questions about two
objects being equal does make sense, but asking about them not being
equal doesn't make sense?

我们经常把这些算符与相等联系起来。尽管这是它们在基本类型上的行为方式,但没有义务将其作为自定义数据类型上的行为。如果你不想的话,你甚至不必还一个乳房。

我看到过人们以奇怪的方式重载操作符,结果发现这对于他们特定于领域的应用程序是有意义的。即使界面显示它们是互斥的,作者也可能希望添加特定的内部逻辑。

(either from the user's perspective, or the implementer's perspective)

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

1
2
3
4
5
6
7
8
9
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 );
}

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


有一些非常成熟的约定,其中(a == b)(a != b)都是both false不一定是对立的。尤其是在SQL中,与空值的任何比较都会产生空值,而不是真值或假值。

如果可能的话,创建新的示例可能不是一个好主意,因为这是非常不具说服力的,但是如果你试图建立一个现有的约定模型,那么让你的操作员在这种情况下"正确地"工作是很好的选择。


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

If there is no such possibility, then why on Earth does C++ have these two operators being defined as two distinct functions?

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

即使在haskell,开发人员非常重视法律和数学概念,仍然允许一个超负荷的==/=,如您所见(http://hackage.haskell.org/package/base-4.9.0.0/docs/prelude.html-v:-61--61-):

1
2
3
4
5
6
7
$ 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'

这可能被视为微观优化,但在某些情况下可能是必要的。


Is there any situation possible in which asking questions about two
objects being equal does make sense, but asking about them not being
equal doesn't make sense? (either from the user's perspective, or the
implementer's perspective)

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


响应编辑;

That is, if it is possible for some type to have the operator == but not the !=, or vice versa, and when does it make sense to do so.

一般来说,不,这没有意义。等式和关系运算符通常以集合形式出现。如果存在相等,那么也存在不等式;对于<=等,小于、大于等。同样,算术运算符也采用类似的方法,它们通常也是自然逻辑集。

这在std::rel_ops名称空间中得到了证明。如果您实现了相等和小于运算符,那么使用该名称空间将为您提供其他名称空间,这些名称空间是根据原始实现的运算符实现的。

所有人都说,是否有条件或情况,其中一个不会立即意味着另一个,或不能执行的其他方面?是的,可以说是很少的,但它们确实存在;同样,正如在rel_ops中所证明的那样,它们本身就是一个名称空间。出于这个原因,允许它们独立地实现允许您利用语言来获得您所需要的语义,对于代码的用户或客户机来说,这种方式仍然是自然和直观的。

上面提到的懒惰的评估就是一个很好的例子。另一个很好的例子是给他们提供了语义,这并不意味着平等或完全平等。与此类似的例子是用于流插入和提取的位移位操作符<<>>。虽然在一般的圈子里人们可能不喜欢它,但在某些领域,它可能是有意义的。


如果==!=运算符实际上并不意味着相等,那么,与<<>>流运算符不意味着位移动相同。如果你把这些符号看作是其他概念的意思,它们就不必互相排斥。

在相等性方面,如果用例保证将对象视为不可比较的对象,那么每个比较都应该返回false(如果运算符返回非bool,则返回不可比较的结果类型),这是有意义的。我想不出一个具体的情况,这是可以保证的,但我可以看到它是合理的足够。


强大的力量带来巨大的责任感,或者至少是很好的风格指南。

==!=可以超载,可以做任何你想做的事情。这既是一种祝福,也是一种诅咒。不能保证!=表示!(a==b)


1
2
3
4
5
6
7
8
enum BoolPlus {
    kFalse = 0,
    kTrue = 1,
    kFileNotFound = -1
}

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

我不能证明这个操作符重载的合理性,但是在上面的示例中,不可能将operator!=定义为operator==的"相反"。


最后,使用这些运算符检查的是表达式a == ba != b返回布尔值(truefalse)。这些表达式在比较后返回布尔值,而不是互相排斥。


[..] why are there two separate definitions needed?

需要考虑的一件事是,实现这些运算符中的一个可能比使用另一个的求反更有效。

(我这里的例子是垃圾,但问题仍然存在,例如,想想布卢姆过滤器:如果某个东西不在一个集合中,它们允许快速测试,但是如果它在集合中,则测试可能需要更多的时间。)

[..] by definition, a != b is !(a == b).

这是你作为程序员的责任。编写测试可能是件好事。


是的,因为一个意思是"等价物",另一个意思是"不等价物",而且这个术语是互斥的。此运算符的任何其他含义都是混淆的,应尽量避免。


通过自定义操作符的行为,可以让它们做您想要做的事情。

您可能希望定制物品。例如,您可能希望自定义一个类。只需检查特定属性,就可以比较此类的对象。既然知道了这一点,您就可以编写一些只检查最小值的特定代码,而不是检查整个对象中每个属性的每一位。

想象一下这样一个例子,在这个例子中,你可以发现有些东西和你发现的东西一样快,甚至不快。当然,一旦你发现某个事物是相同的还是不同的,那么你只需轻轻地翻转一下就可以知道相反的情况。然而,翻转该位是一个额外的操作。在某些情况下,当代码被重新执行很多次时,保存一个操作(乘以多次)可能会提高整体速度。(例如,如果您在百万像素屏幕上每像素保存一个操作,那么您刚刚保存了一百万个操作。乘以每秒60个屏幕,您可以保存更多操作。)

a href="http://stackoverflow.com/a/37800271/4411648">hvd的答案/aa提供了一些其他示例。


也许这是一个不可原谅的规则,其中a != b是假的,a == b是假的,就像一个无状态的比特。

1
2
3
if( !(a == b || a != b) ){
    // Stateless
}