6

The definition of spaceship operator is meant to have a strong definition of ordering, but does this affect the way your client code is written or just how to define your class comparison operators?

Since in other post are missing real world example I'm not fully understanding this part.

Other SO post about spaceship operator:

Moia
  • 2,216
  • 1
  • 12
  • 34
  • For ordered types it will produce as strcmp: a value that indicate whether the comparison result in greater smaller or equal. So you can look for example of strmcp. For unordered type it can also provide the case where two object are unordered (like 2 infinity, or comparison with nan) this may help coder in not forgetting this case. – Oliv Jan 23 '19 at 11:53
  • 4
    What do you define as **client code**? And why is a class declaration excluded from it? – StoryTeller - Unslander Monica Jan 23 '19 at 11:55
  • I might be opinionated, but I dislike the `<=>` operator. They could've made `>`, `>=`, `<=`, `!=` implicit by having defined only `==` and `<`... The way `<=>` is specified is way too complicated (even its own header and library components). – DeiDei Jan 23 '19 at 11:59
  • 3
    @DeiDei `auto X::operator<=>(const Y&) =default;` is *very* simple. – Caleth Jan 23 '19 at 12:35
  • @Moia the comment is a reply to DeiDei, showing that the complexity of the specification give simplicity of use. The "client code" for `<=>` is the union of the "client code" for `<`, `<=`, `>`, `>=`, `==`, and `!=` – Caleth Jan 23 '19 at 12:51
  • @StoryTeller I'd like to see a _Minimal, Complete, and Verifiable example_ of how to use it. – Moia Jan 23 '19 at 12:52
  • 2
    @DeiDei The question is: how do you define `<=`? If you're suggesting that `a <= b` is `!(b < a)`, then that gives the wrong answer for partial orders (e.g. `float`). If you're suggesting that `a <= b` is `(a < b) || (a == b)`, then that always gives the right answer but is potentially a large pessimization for total orders (e.g. `string`). `<=>` is a hugely superior choice to `<` to use as an ordering operator. – Barry Jan 23 '19 at 13:31

2 Answers2

5

You just compare the way you've always done:

a < b

It's just that under the hood, one of the candidate functions for that expression will also find (a <=> b) < 0 and if that candidate exists and happens to be the best viable candidate, then it is invoked.

You typically don't use <=> directly in "client code", you just use the comparisons that you want directly.

For instance, given:

struct X {
    int i;

    // this can be = default, just writing it out for clarity
    strong_ordering operator<=>(X const& rhs) const { return i <=> rhs.i; }
};

The expression

X{42} < X{57};

will evaluate as X{42} <=> X{57} < 0 (there is no < candidate, so <=> non-reversed is trivially the best candidate). X{42} <=> X{57} evaluates as 42 <=> 57 which is strong_ordering::less. And that < 0 returns true. Hence, the initial expression is true... as expected.

The same operator also directly gives us that X{57} > X{42}, that X{3} >= X{2}, etc.


The advantage of <=> is that you only need to write one operator instead of four, that operator is typically much easier to write than <, you can properly express the differentiation between partial and total orders, and stacking it is typically more performant (e.g. in the cases like string).

Additionally, we don't have to live in this weird world where everyone pretends that operator< is the only relational operator that exists.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    And if all comparison operator depends on the same call to a function and a comparison with a fundamental type, the optimizer will be able to use its knowledge of the mathematical structure of integer with comparison operator to optimize further the code:https://godbolt.org/z/2ip7JK – Oliv Jan 23 '19 at 15:29
  • "The advantage of <=> is that you only need to write one operator instead of four" - shouldn't that be *six*? `<`, `<=`, `==`, `>=`, `>`, and `!=`? Or did I miss something? – cmaster - reinstate monica Apr 17 '20 at 10:25
  • @cmaster-reinstatemonica No, four. `<=>` is an ordering operator, it doesn't provide equality. `==` gives you `==` and `!=`. – Barry Apr 17 '20 at 14:18
  • Ok, once again there is stuff in C++ that I fail to understand. I mean, why introduce enum values for `equivalent`, `less`, `greater` and `unordered` (the `partial_ordering` type) if no sensible `==` and `!=` operators are defined? It's once again going only part of the way, leaving us with a suboptimal solution that will spawn suboptimal code, which in turn will hinder further progress of the language :-( Imho, if a language adds a feature, it should embrace that feature whole-heartedly. A patchwork that only applies features to parts of the language can only produce Frankenstein monsters. – cmaster - reinstate monica Apr 17 '20 at 14:44
  • Well, if the `<=>` based version of `==` is not fast enough, I'm always free to implement an explicit `==`. That's an optimization. The `<=>` exists to simplify things, but it leaves the programmer with the power to fall back to doing it all themselves should need be. It's not the first simplification in C++ that may turn into a pessimisation. I do not believe that forcing programmers to needlessly provide explicit `==` operators is an improvement of an otherwise elegant, and long awaited feature. – cmaster - reinstate monica Apr 17 '20 at 16:09
  • @cmaster-reinstatemonica Okay, you do that. Now I have your type as a member. So now I have to write an explicit `==` that just calls your `==`, otherwise I get the slow one. So, okay, hopefully I'm diligent enough about this. So I do that. Okay, now somebody has my type as a member - and they have to know to write an explicit `==` that just calls my `==` that just calls your `==`... otherwise _they_ get the slow one. And ... – Barry Apr 17 '20 at 16:16
  • @cmaster-reinstatemonica And if I forget (or don't know to, or don't think to) add that explicit `==` that calls your `==`... then that somebody else that uses my type as a member is just screwed. Even if they write `==`, it would still be slow. – Barry Apr 17 '20 at 16:19
  • Fair point, though I don't believe that it really cuts it: How frequently do you write such nested comparison functions? I for one don't do that often. In fact, I cannot remember *ever* writing more than a 2-level comparison. And going over the software projects that I have worked on, no, none ever called for this. So, while you have a point in principle, I really don't see the practical relevance of it. I do see the practical relevance of an automatically defined `==` operator that is guaranteed to agree with the comparison operators which are defined based on my `<=>` implementation, though. – cmaster - reinstate monica Apr 17 '20 at 18:14
  • @cmaster-reinstatemonica What do you mean by a "nested comparison" function? I'm just talking about having a type with some members, whose comparison is just comparing those members. That's... very common. And you _can_ write `bool operator==(T x, T y) { return (x <=> y) == 0; }` if you choose to do so. – Barry Apr 17 '20 at 19:18
  • @cmaster-reinstatemonica Moreover, every class template would need to provide both anyway. – Barry Apr 17 '20 at 19:27
  • Yes, you are right, that trivial implementation of `==` is indeed quickly written, so my point is rather mute :-) And yes, you understood nested type comparisons for what I meant, and yes, they happen. One class comparing all its members in order. That's fine with me. However, one class comparing its members which need to compare their non-POD members in turn, that's where the real nesting begins and which is extremely rare. And why would every class template need to provide comparisons? I mean, I don't want to compare complex stuff for no good reason, and most stuff never needs comparisons. – cmaster - reinstate monica Apr 17 '20 at 21:09
  • However, the point of my "rant" above was not so much about C++ and its `<=>` operator, but more generally about the accumulation of corner cases in a language. I happen to have some experience with Fortran. Unfortunately. "Modern" Fortran is an extremely complex language with tons and tons of features, but all features have some corner cases and pitfalls where they act up when you try to combine them with other features. It makes programming in Fortran feel like walking through a mine field. It'll all blow up when you least expect it. And it pains me to see signs of that same disease in C++. – cmaster - reinstate monica Apr 17 '20 at 21:15
4

<=> allows the lazy way to also be the performant way. You don't change your client code.

Clients may see performance benefits when there was a using std::rel_ops (or boost::ordered etc).

An example

// old and busted
struct Person : boost::totally_ordered<Person>
{
    std::string firstname;
    std::string lastname
    bool operator<(const Person & other) 
    { 
        return std::tie(firstname, lastname)
             < std::tie(other.firstname, other.lastname); 
    }
}

// new hotness
struct Person
{
    std::string firstname;
    std::string lastname;
    auto operator<=>(const Person &) = default;
}

int main()
{
    Person person1 { "John", "Smith" };
    Person person2 { "John", "Smith" };
    std::cout << person2 <= person1 << std::endl;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75