等于运算符重载:是(x!= y)==(!(x == y))?

Mar*_*o13 26 c++ language-lawyer equals-operator

我希望这不是重复 - 我搜索了关于相等运算符的其他问题,但除了相关问题中的一些评论之外,我没有找到明确的陈述


TL;博士:

C++标准是否保证(x!=y)始终具有相同的真值!(x==y)


我知道有很多微妙之处这里涉及:经营者==!=可能超载.它们可能会被重载以具有不同的返回类型(只需要隐式转换为bool).甚至!-operator也可能在返回类型上重载.这就是为什么我手动提到上面的"真值",但试图进一步阐述它,利用隐式转换bool,并试图消除可能的含糊之处:

bool ne = (x!=y);
bool e = (x==y);
bool result = (ne == (!e));
Run Code Online (Sandbox Code Playgroud)

result保证是true在这里吗?

C++标准在5.10节中指定了相等运算符,但主要似乎是在语法上定义它们(以及关于指针比较的一些语义).该概念EqualityComparable的存在,但人们对其经营者的关系,没有专门的语句==!=运营商.

C++工作组存在相关文档,说......

至关重要的是,相等/不相等[...]表现为彼此的布尔否定.毕竟,如果operator ==()和operator!=()都返回false,那么世界就没有意义了!因此,通常相互实现这些运算符

但是,这仅反映了Common Sense™,并没有指定它们必须像这样实现.


一些背景:我只是试图编写一个函数来检查两个值(未知类型)是否相等,如果不是这样,则打印错误消息.我想说这里所需的概念是类型EqualityComparable.但是对于这个,人们仍然必须写if (!(x==y)) {...},不能if (x!=y) {...},因为这将使用一个不同的运算符,根本没有概念,EqualityComparable甚至可能不同的重载...


更新

我知道程序员基本上可以在自定义重载中做任何他想做的事情.我只是想知道他是否真的被允许做所有事情,或者是否有标准规定的规则.也许这些微妙的陈述中的一个暗示偏离通常的实现会导致不确定的行为,就像NathanOliver在评论中提到的那样,但似乎只是指某些类型.例如,标准明确规定对于容器类型,a!=b等同于!(a==b)(第23.2.1节,表95,"容器要求").

但对于一般的,用户定义的类型,目前似乎没有这样的要求.这个问题被贴上了标签language-lawyer,因为我希望有一个明确的陈述/参考,但我知道这几乎是不可能的:虽然人们可以指出它所说的经营者必须互相否定的部分,但很难证明标准的~1500页中没有一个说出这样的东西......

有疑问,除非有进一步的提示,否则我将在稍后提出/接受相应的答案,并且现在假设为了比较EqualityComparable类型的不相等应该是为了if (!(x==y))安全起见.

Bar*_*rry 20

C++标准是否保证(x!=y)始终具有相同的真值!(x==y)

不,不.绝对没有什么能阻止我写作:

struct Broken {
    bool operator==(const Broken& ) const { return true; }
    bool operator!=(const Broken& ) const { return true; }
};

Broken x, y;
Run Code Online (Sandbox Code Playgroud)

这是完美的代码.在语义上,它已经破碎(顾名思义),但从纯C++代码功能的角度来看,它肯定没有错.

标准也清楚地表明在[over.oper]/7中这是可以的:

应用于基本类型的某些预定义运算符之间的标识(例如,++a ? a+=1)不需要保持运算符函数.一些预定义的运算符,例如+=,当应用于基本类型时,需要操作数作为左值; 这不是操作员功能所必需的.

同样,C++标准中没有任何内容保证operator<实际实现有效的Ordering(或者x<y <==> !(x>=y)等等).一些标准库实现实际上会添加检测以尝试在订购的容器中为您调试,但这只是实现质量问题,而不是基于标准的决策.


boost操作符这样的库解决方案至少可以让程序员更轻松一点:

struct Fixed : equality_comparable<Fixed> {
    bool operator==(const Fixed&) const;
    // a consistent operator!= is provided for you
};
Run Code Online (Sandbox Code Playgroud)

虽然现在Fixed不再是一个聚合.所以它仍然不是一个理想的解决方案.虽然P0017显然已被C++ 17接受,但它允许列表初始化ne().


Ste*_*ven 13

在一般情况下,我不认为你可以依靠它,因为它并不总是有意义的operator ==,并operator!=对应,所以我看不出标准可能永远需要它.

例如,考虑内置浮点类型,如双精度,NaN总是比较为false,因此operator ==和operator!=都可以同时返回false.(编辑:糟糕,这是错误的;请参阅hvd的评论.)

因此,如果我正在编写一个具有浮点语义的新类(可能是一个really_long_double),我必须实现相同的行为以与原始类型保持一致,所以我operator==必须表现相同并比较两个NaN作为假,即使他们operator!=也将它们比作假.

在其他情况下也可能出现这种情况.例如,如果我正在编写一个表示数据库可空值的类,那么我可能会遇到同样的问题,因为所有与数据库NULL的比较都是错误的.我可能会选择在我的C++代码中实现该逻辑,以使其具有与数据库相同的语义.

但实际上,对于您的用例,可能不值得担心这些边缘情况.只记录您的函数比较使用的对象operator== (or operator !=)并保留它.