繁体   English   中英

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

[英]Equality operator overloads: Is (x!=y) == (!(x==y))?

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


我知道这里涉及许多微妙之处:运算符==!=可能会被重载。 它们可能被重载以具有不同的返回类型(只需隐式转换为bool )。 甚至! -operator 可能在返回类型上重载。 这就是为什么我手摇地提到上面的“真值”,但试图进一步阐述它,利用隐式转换为bool ,并试图消除可能的歧义:

bool ne = (x!=y);
bool e = (x==y);
bool result = (ne == (!e));

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类型的EqualityComparable应该使用if (!(x==y))来完成在安全方面。

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

不,它没有。 绝对没有什么能阻止我写:

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

Broken x, y;

那是完全格式良好的代码。 从语义上讲,它被破坏了(顾名思义),但从纯 C++ 代码功能的角度来看,它肯定没有错。

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

应用于基本类型(例如, ++a ≡ a+=1 )的某些预定义运算符之间的身份不需要对运算符函数保持不变。 一些预定义的运算符(例如+=在应用于基本类型时要求操作数为左值; 这不是操作员功能所要求的。

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


Boost.Operators这样的库解决方案的存在至少可以让程序员更容易一些:

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

在 C++14 中, Fixed不再是与基类的聚合。 但是,在 C++17 中,它又是一个聚合(通过P0017 )。


随着 C++20 采用P1185 ,库解决方案已经有效地变成了一种语言解决方案——你只需要这样写:

struct Fixed {
    bool operator==(Fixed const&) const;
};

bool ne(Fixed const& x, Fixed const& y) {
    return x != y;
}

ne()的主体成为计算为!x.operator==(y)的有效表达式——因此您不必担心保持两个比较一致,也不必依赖库解决方案来提供帮助。

一般而言,我认为您不能依赖它,因为operator ==operator!=始终对应并不总是有意义,所以我不知道标准如何要求它。

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

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

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

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

不可以。您可以为==!=编写运算符重载来做任何您想做的事。 这样做可能是个坏主意,但 C++ 的定义并没有将这些运算符限制为彼此的逻辑对立。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM