Author Topic: Spaceship Operator  (Read 3940 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Spaceship Operator
« on: December 10, 2018, 03:39:38 PM »
Ohh, the Spaceship operator <=> is coming to C++20.

This allows you to define a single three-way comparison operator <=>, and the compiler can auto generate all other comparison operators from it: ==, !=, <, >, <=, >=

Example (from cppreference.com):
Code: cpp [Select]

class Point {
 int x;
 int y;
public:
 auto operator<=>(const Point&) const = default;
 /* ... non-comparison functions ... */
};

/* compiler generates all six relational operators */
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } /* ok */
std::set<Point> s; /* ok */
s.insert(pt1); /* ok */
if (pt1 <= pt2) { /*...*/ } /* ok, makes only a single call to <=>  */


References:
cppreference.com - Default Comparisons
cppreference.com - Comparison Operators - Three Way Comparison
Library Support for the Spaceship (Comparison) Operator


EDIT (leeor_net): Fix code syntax highlighting.
« Last Edit: December 10, 2018, 07:48:14 PM by leeor_net »

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2350
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: Spaceship Operator
« Reply #1 on: December 10, 2018, 07:48:37 PM »
This is exciting. Having to hand write those freaking operators sucks.

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Re: Spaceship Operator
« Reply #2 on: December 10, 2018, 08:33:03 PM »
How does it know when one point is greater than or less than another point without some more code to tell the compiler?

-Brett

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2350
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: Spaceship Operator
« Reply #3 on: December 10, 2018, 09:39:54 PM »
Probably by doing one operator in terms of the other.

E.g., in many implementations of operator> and operator<, the code looks something like this:

Code: [Select]
bool Class::operator>(c)
{
    /* some comparison of members here */
    return true;
}

Class::operator<(c)
{
    return this > c;
}

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1013
Re: Spaceship Operator
« Reply #4 on: December 10, 2018, 10:04:31 PM »
But that doesn't tell you which point is actually greater than the other point? The spaceship operator needs some help to understand what greater than or less than means for a point class right?

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Re: Spaceship Operator
« Reply #5 on: December 11, 2018, 04:09:42 AM »
It's a lot like how strcmp works. You implement the one operator, the spaceship operator, which is effectively 3-valued:
  • a < b  : (a <=> b) < 0
  • a == b  : (a <=> b) == 0
  • a > b  : (a <=> b) > 0

Then, using that single value, from a single operator call, you can determine (compiler generated):
  • a == b  : (a <=> b) == 0
  • a != b  : (a <=> b) != 0
  • a < b  : (a <=> b) < 0
  • a > b  : (a <=> b) > 0
  • a <= b  : (a <=> b) <= 0
  • a >= b  : (a <=> b) >= 0

So effectively you get all 6 comparison operators, but only have to implement one.



Previously they had std::rel_ops. Given two operators operator==, and operator<, it would implement the other 4 for you. The problem with std::rel_ops is it didn't play nicely with ADT (Argument-Dependent Lookup), so sometimes those operators weren't found. Additionally, it added extra template operators to the global namespace, so all structs would end up getting the extra comparison operators, whether you wanted them or not.

The implementation of std::rel_ops might use the following equivalences:
  • a != b  : !(a == b)
  • a > b  : (b < a)
  • a <= b  : !(a > b)
  • a >= b  : !(a < b)

Note: The above also only require one operator call.

A naive implementation might call two possibly expensive operators:
  • a <= b  : (a < b) || (a == b)
You want to avoid that.



Another benefit of the <=> operator, is that it can distinguish between the various relational strengths, and only provide overloads that are appropriate. That's a whole other topic, but here are the orderings:
  • strong ordering
  • weak ordering
  • partial ordering
  • strong equality
  • weak equality




Edit: In regards to a "point" (2D, 3D, etc.), there may be no natural way to do inequality comparisons. Hence the different orderings above, which may exclude auto generation of some comparison operators. In particular, if the return type of the spaceship operator is of the last 2 types, the compiler knows not to generate inequality comparisons.
« Last Edit: December 11, 2018, 06:24:00 AM by Hooman »