Non-Defaulted Operator <=> Doesn't Generate == and != in C++20

non-defaulted operator = doesn't generate == and != in C++20

This is by design.

[class.compare.default] (emphasis mine)

3 If the class definition does not explicitly declare an ==
operator function, but declares a defaulted three-way comparison
operator
function, an == operator function is declared implicitly
with the same access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline
member and is defined as defaulted in the definition of X.

Only a defaulted <=> allows a synthesized == to exist. The rationale is that classes like std::vector should not use a non-defaulted <=> for equality tests. Using <=> for == is not the most efficient way to compare vectors. <=> must give the exact ordering, whereas == may bail early by comparing sizes first.

If a class does something special in its three-way comparison, it will likely need to do something special in its ==. Thus, instead of generating a potentially non-sensible default, the language leaves it up to the programmer.

!= auto generated from == in C++20?

!= auto generated from == in C++20?

Is this standard behavior in C++20?

Yes. operator!= is auto generated from operator== in C++20.

Furthermore, all four relational operators are generated if you define operator<=>, and all of the comparison operators are generated if you define operator<=> as defaulted.

What you want to do in most cases:

struct example
{
std::string a;
int b;

auto operator<=>(const example&) const = default;
};

Why don't C++ compilers define operator== and operator!=?

The compiler wouldn't know whether you wanted a pointer comparison or a deep (internal) comparison.

It's safer to just not implement it and let the programmer do that themselves. Then they can make all the assumptions they like.

Which operators implictly define / generate other operators in C++?

Which operators implicitly define / generate other operators in C++?

There is only one situation in which one operator defines/generates another, and that is when you default operator<=> you also get a defaulted operator==. That's the complete list.

Everything else is not based on declaring operators, it is based on rewriting expressions:

  • it's not that an operator!= is generated from operator==, it's that the expression x != y also tries to evaluate as !(x == y)
  • it's not that an operator< is generated from operator<=>, it's that the expression x < y also tries to evaluate as (x <=> y) < 0

In your case, f == g simply has no operator== candidate, so it's ill-formed. The original design of <=> also would try to rewrite this expression as (f <=> g) == 0 (again, not generating operator== but rather rewriting the expression). But this was shown to have serious performance issues and so it was changed to not do this. You can read more about Comparisons in C++20 here.

In this case, since you're doing member-wise comparison, you can simply:

bool operator==(Foo const&) const = default;

or write it manually if you prefer. Either way, your operator<=> is missing a const - it's important that the comparison operators be symmetric.

Why default three-way operator (spaceship =) generates equality operator (==) and user define three-way operator not?

The principle reason why equality and ordering are separated is performance. If you have a type whose ordering operations are user-defined, then more often than not, you can write a user-defined equality test operation that is more efficient at doing equality tests. And therefore, the language should encourage you to write it by not using operator<=> for direct equality testing.

This only really applies to user-defined ordering/equality operations. Default ordering is member-wise, and default equality operations are also member-wise. And since ordering implies equality, it is reasonable that defaulting ordering also defaults equality.

Yes, they could make people spell it out, but there wasn't really a good reason for that. operator<=> was meant to make it easy to opt-in to default ordering; making you write two declarations for something that's already implied by one of them doesn't make sense.

Why must I provide 'operator ==' when 'operator =' is enough?

Why must I provide operator== when operator<=> is enough?

Well, mainly because it's not enough :-)

Equality and ordering are different buckets when it comes time for C++ to rewrite your statements:

EqualityOrdering
Primary==<=>
Secondary!=<, >, <=, >=

Why is operator!= removed in C++20 for many standard library types?

In C++20 the way that the relational operators work was changed, notably with the introduction of the spaceship <=> operator. In particular, If you only provide operator==, then a != b is rewritten to !(a == b).

From [over.match.oper]/3.4:

The rewritten candidate set is determined as follows:

  • For the relational ([expr.rel]) operators, the rewritten candidates include all non-rewritten candidates for the expression x <=> y.
  • For the relational ([expr.rel]) and three-way comparison ([expr.spaceship]) operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y <=> x.
  • For the != operator ([expr.eq]), the rewritten candidates include all non-rewritten candidates for the expression x == y.
  • For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
  • For all other operators, the rewritten candidate set is empty.

And [over.match.oper]/9:

If a rewritten operator== candidate is selected by overload resolution for an operator @, its return type shall be cv bool, and x @ y is interpreted as:

  • if @ is != and the selected candidate is a synthesized candidate with reversed order of parameters, !(y == x),
  • otherwise, if @ is !=, !(x == y),
  • otherwise (when @ is ==), y == x,

in each case using the selected rewritten operator== candidate.

As such, an explicit overload for operator!= is no longer necessary. The removal of the operator has not changed comparison semantics.

All containers have had their operator!= removed, as far as I can tell (check e.g. the vector synopsis). The only exceptions are the container adaptors std::queue and std::stack: my guess is that it is to preserve backwards compatibility when used with third-party containers, in case the equality operators are not symmetric.



Related Topics



Leave a reply



Submit