相等运算符重载:是 (x!=y) == (!(x==y))?
Equality operator overloads: Is (x!=y) == (!(x==y))?
C++ 标准是否保证 (x!=y)
总是与 !(x==y)
具有相同的真值?
我知道这里涉及 许多 微妙之处:运算符 ==
和 !=
可能被重载。它们可能被重载以具有不同的 return 类型(只需要隐式转换为 bool
)。甚至 !
运算符也可能在 return 类型上重载。这就是为什么我手忙脚乱地提到了上面的“真值”,但试图进一步阐述它,利用到 bool
的隐式转换,并试图消除可能的歧义:
bool ne = (x!=y);
bool e = (x==y);
bool result = (ne == (!e));
这里result
保证是true
吗?
C++ 标准在第 5.10 节中指定了相等运算符,但似乎主要是在语法上定义它们(以及一些关于指针比较的语义)。 存在EqualityComparable的概念,但没有专门说明其运算符==
与!=
运算符的关系。
存在related documents from C++ working groups,说...
It is vital that equal/unequal [...] behave as boolean negations of each other. After all, the world would make no sense if both operator==() and operator!=() returned false! As such, it is common to implement these operators in terms of each other
然而,这仅反映 Common Sense™,并没有指定它们必须像这样实施。
一些背景知识:我只是想编写一个函数来检查两个值(未知类型)是否相等,如果不相等则打印一条错误消息。我想说这里需要的概念是类型是EqualityComparable
。但是为此,人们仍然必须写 if (!(x==y)) {…}
并且可以 而不是 写 if (x!=y) {…}
,因为这将使用不同的运算符,这不包含在概念中EqualityComparable
,甚至可能以不同的方式重载...
我知道程序员基本上可以在他的自定义重载中为所欲为。我只是想知道他是否真的允许做任何事情,或者是否有标准规定的规则。也许这些微妙的陈述之一表明偏离通常的实现会导致未定义的行为,例如 。例如,标准 明确地 声明对于 容器类型 ,a!=b
等同于 !(a==b)
(第 23.2.1 节, table 95, "容器要求").
但是对于一般的、自定义的类型,目前好像没有这样的要求。这个问题被标记为 language-lawyer
,因为我希望得到一个明确的 statement/reference,但我知道这几乎是不可能的:虽然有人可以指出其中表示运算符 的部分 是彼此的否定,很难证明标准的 ~1500 页中的 none 是这样说的...
有疑问,除非有进一步的提示,否则我稍后会 upvote/accept 相应的答案,现在假设比较 EqualityComparable
类型的不相等应该用 if (!(x==y))
为了安全起见。
Does the C++ standard guarantee that (x!=y)
always has the same truth value as !(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中也明确指出这是可以的:
The identities among certain predefined operators applied to basic types (for example, ++a ≡ a+=1
) need not hold for operator functions. Some predefined operators, such as +=
, require an operand to be an lvalue when applied to basic types; this is not required by operator functions.
同样,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
不再是基数 class 的聚合。但是,在 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)
——因此您不必担心保持两个比较一致,也不必依赖库解决方案来提供帮助出去。
没有。您可以为 ==
和 !=
编写运算符重载来执行您想要的任何操作。这样做可能不是一个好主意,但 C++ 的定义并没有将这些运算符限制为彼此的逻辑对立。
总的来说,我不认为你可以依赖它,因为它并不总是对 operator ==
和 operator!=
到 always[=29= 有意义] 对应,所以我不明白标准怎么会要求它。
例如, 考虑内置浮点类型,如双精度数,NaNs 总是比较 false,因此 operator= = 和 operator!= 可以同时 return false。 (编辑:糟糕,这是错误的;请参阅 hvd 的评论。)
因此,如果我正在编写一个具有浮点语义的新 class(可能是 really_long_double),我 有 来实现相同的行为与原始类型一致,所以我的 operator==
必须表现相同并将两个 NaN 比较为 false,即使 operator!=
也将它们比较为 false。
在其他情况下也可能会出现这种情况。例如,如果我正在编写一个 class 来表示一个数据库可为 null 的值,我可能会 运行 进入同一个问题,因为所有与数据库 NULL 的比较都是错误的。我可能会选择在我的 C++ 代码中实现该逻辑以具有与数据库相同的语义。
但实际上,对于您的用例,可能不值得担心这些边缘情况。只需记录您的函数使用 operator== (or operator !=)
比较对象并保留它。
C++ 标准是否保证 (x!=y)
总是与 !(x==y)
具有相同的真值?
我知道这里涉及 许多 微妙之处:运算符 ==
和 !=
可能被重载。它们可能被重载以具有不同的 return 类型(只需要隐式转换为 bool
)。甚至 !
运算符也可能在 return 类型上重载。这就是为什么我手忙脚乱地提到了上面的“真值”,但试图进一步阐述它,利用到 bool
的隐式转换,并试图消除可能的歧义:
bool ne = (x!=y);
bool e = (x==y);
bool result = (ne == (!e));
这里result
保证是true
吗?
C++ 标准在第 5.10 节中指定了相等运算符,但似乎主要是在语法上定义它们(以及一些关于指针比较的语义)。 存在EqualityComparable的概念,但没有专门说明其运算符==
与!=
运算符的关系。
存在related documents from C++ working groups,说...
It is vital that equal/unequal [...] behave as boolean negations of each other. After all, the world would make no sense if both operator==() and operator!=() returned false! As such, it is common to implement these operators in terms of each other
然而,这仅反映 Common Sense™,并没有指定它们必须像这样实施。
一些背景知识:我只是想编写一个函数来检查两个值(未知类型)是否相等,如果不相等则打印一条错误消息。我想说这里需要的概念是类型是EqualityComparable
。但是为此,人们仍然必须写 if (!(x==y)) {…}
并且可以 而不是 写 if (x!=y) {…}
,因为这将使用不同的运算符,这不包含在概念中EqualityComparable
,甚至可能以不同的方式重载...
我知道程序员基本上可以在他的自定义重载中为所欲为。我只是想知道他是否真的允许做任何事情,或者是否有标准规定的规则。也许这些微妙的陈述之一表明偏离通常的实现会导致未定义的行为,例如 a!=b
等同于 !(a==b)
(第 23.2.1 节, table 95, "容器要求").
但是对于一般的、自定义的类型,目前好像没有这样的要求。这个问题被标记为 language-lawyer
,因为我希望得到一个明确的 statement/reference,但我知道这几乎是不可能的:虽然有人可以指出其中表示运算符 的部分 是彼此的否定,很难证明标准的 ~1500 页中的 none 是这样说的...
有疑问,除非有进一步的提示,否则我稍后会 upvote/accept 相应的答案,现在假设比较 EqualityComparable
类型的不相等应该用 if (!(x==y))
为了安全起见。
Does the C++ standard guarantee that
(x!=y)
always has the same truth value as!(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中也明确指出这是可以的:
The identities among certain predefined operators applied to basic types (for example,
++a ≡ a+=1
) need not hold for operator functions. Some predefined operators, such as+=
, require an operand to be an lvalue when applied to basic types; this is not required by operator functions.
同样,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
不再是基数 class 的聚合。但是,在 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)
——因此您不必担心保持两个比较一致,也不必依赖库解决方案来提供帮助出去。
没有。您可以为 ==
和 !=
编写运算符重载来执行您想要的任何操作。这样做可能不是一个好主意,但 C++ 的定义并没有将这些运算符限制为彼此的逻辑对立。
总的来说,我不认为你可以依赖它,因为它并不总是对 operator ==
和 operator!=
到 always[=29= 有意义] 对应,所以我不明白标准怎么会要求它。
例如, 考虑内置浮点类型,如双精度数,NaNs 总是比较 false,因此 operator= = 和 operator!= 可以同时 return false。 (编辑:糟糕,这是错误的;请参阅 hvd 的评论。)
因此,如果我正在编写一个具有浮点语义的新 class(可能是 really_long_double),我 有 来实现相同的行为与原始类型一致,所以我的 operator==
必须表现相同并将两个 NaN 比较为 false,即使 operator!=
也将它们比较为 false。
在其他情况下也可能会出现这种情况。例如,如果我正在编写一个 class 来表示一个数据库可为 null 的值,我可能会 运行 进入同一个问题,因为所有与数据库 NULL 的比较都是错误的。我可能会选择在我的 C++ 代码中实现该逻辑以具有与数据库相同的语义。
但实际上,对于您的用例,可能不值得担心这些边缘情况。只需记录您的函数使用 operator== (or operator !=)
比较对象并保留它。