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

安稳与你 提交于 2019-12-01 13:42:52

问题


Does the C++ standard guarantee that (x!=y) always has the same truth value as !(x==y)?


I know there are many subtleties involved here: The operators == and != may be overloaded. They may be overloaded to have different return types (which only have to be implicitly convertible to bool). Even the !-operator might be overloaded on the return type. That's why I handwavingly referred to the "truth value" above, but trying to elaborate it further, exploiting the implicit conversion to bool, and trying to eliminate possible ambiguities:

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

Is result guaranteed to be true here?

The C++ standard specifies the equality operators in section 5.10, but mainly seems to define them syntactically (and some semantics regarding pointer comparisons). The concept of being EqualityComparable exists, but there is no dedicated statement about the relationship of its operator == to the != operator.

There exist related documents from C++ working groups, saying that...

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

However, this only reflects the Common Sense™, and does not specify that they have to be implemented like this.


Some background: I'm just trying to write a function that checks whether two values (of unknown type) are equal, and print an error message if this is not the case. I'd like to say that the required concept here is that the types are EqualityComparable. But for this, one would still have to write if (!(x==y)) {...} and could not write if (x!=y) {...}, because this would use a different operator, which is not covered with the concept of EqualityComparable at all, and which might even be overloaded differently...


I know that the programmer basically can do whatever he wants in his custom overloads. I just wondered whether he is really allowed to do everything, or whether there are rules imposed by the standard. Maybe one of these subtle statements that suggest that deviating from the usual implementation causes undefined behavior, like the one that NathanOliver mentioned in a comment, but which seemed to only refer to certain types. For example, the standard explicitly states that for container types, a!=b is equivalent to !(a==b) (section 23.2.1, table 95, "Container requirements").

But for general, user-defined types, it currently seems that there are no such requirements. The question is tagged language-lawyer, because I hoped for a definite statement/reference, but I know that this may nearly be impossible: While one could point out the section where it said that the operators have to be negations of each other, one can hardly prove that none of the ~1500 pages of the standard says something like this...

In doubt, and unless there are further hints, I'll upvote/accept the corresponding answers later, and for now assume that for comparing not-equality for EqualityComparable types should be done with if (!(x==y)) to be on the safe side.


回答1:


Does the C++ standard guarantee that (x!=y) always has the same truth value as !(x==y)?

No it doesn't. Absolutely nothing stops me from writing:

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

Broken x, y;

That is perfectly well-formed code. Semantically, it's broken (as the name might suggest), but there's certainly nothing wrong from it from a pure C++ code functionality perspective.

The standard also clearly indicates this is okay in [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.

In the same vein, nothing in the C++ standard guarantees that operator< actually implements a valid Ordering (or that x<y <==> !(x>=y), etc.). Some standard library implementations will actually add instrumentation to attempt to debug this for you in the ordered containers, but that is just a quality of implementation issue and not a standards-compliant-based decision.


Library solutions like Boost.Operators exist to at least make this a little easier on the programmer's side:

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

In C++14, Fixed is no longer an aggregate with the base class. However, in C++17 it's an aggregate again (by way of P0017).


With the adoption of P1185 for C++20, the library solution has effectively becomes a language solution - you just have to write this:

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

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

The body of ne() becomes a valid expression that evaluates as !x.operator==(y) -- so you don't have to worry about keeping the two comparison in line nor rely on a library solution to help out.




回答2:


In general, I don't think you can rely on it, because it doesn't always make sense for operator == and operator!= to always correspond, so I don't see how the standard could ever require it.

For example, consider the built-in floating point types, like doubles, for which NaNs always compare false, so operator== and operator!= can both return false at the same time. (Edit: Oops, this is wrong; see hvd's comment.)

As a result, if I'm writing a new class with floating point semantics (maybe a really_long_double), I have to implement the same behaviour to be consistent with the primitive types, so my operator== would have to behave the same and compare two NaNs as false, even though operator!= also compares them as false.

This might crop up in other circumstances, too. For example, if I was writing a class to represent a database nullable value I might run into the same issue, because all comparisons to database NULL are false. I might choose to implement that logic in my C++ code to have the same semantics as the database.

In practice, though, for your use case, it might not be worth worrying about these edge cases. Just document that your function compares the objects using operator== (or operator !=) and leave it at that.




回答3:


No. You can write operator overloads for == and != that do whatever you wish. It probably would be a bad idea to do so, but the definition of C++ does not constrain those operators to be each other's logical opposites.



来源:https://stackoverflow.com/questions/35943551/equality-operator-overloads-is-x-y-x-y

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!