62

There's a new comparison operator <=> in C++20. However I think in most cases a simple subtraction works well:

int my_strcmp(const char *a, const char *b) {
    while (*a == *b && *a != 0 && *b != 0) {
        a++, b++;
    }
    // Version 1
    return *a - *b;
    // Version 2
    return *a <=> *b;
    // Version 3
    return ((*a > *b) - (*a < *b));
}

They have the same effect. I can't really understand the difference.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
iBug
  • 35,554
  • 7
  • 89
  • 134
  • 25
    Integer subtraction is an old hack to perform 3-way comparison, but it may suffer from overflow. It doesn't work *always* for unsigned types either. An alternative way is ((*a > *b) - (*a < *b)) – miravalls Dec 31 '17 at 13:48
  • There was even talk of allowing any type with a defaulted `<=>` to be a non-type template parameter. This operator has consequences beyond being a replacement for one operation that "works" only on arithmetic types. – chris Dec 31 '17 at 14:00
  • 1
    @iBug: So... what exactly do you plan to do for doing 3-way comparisons on things that *aren't* arrays of characters? – Nicol Bolas Dec 31 '17 at 15:23
  • As you said, in most cases a simple subtraction works well. What about all the other cases? – edc65 Dec 31 '17 at 17:55
  • @edc65 That's what I'm asking – iBug Jan 01 '18 at 04:13
  • You are lucky in the sense that the domains you work with are small. You could just write a test that exhaustively verifies whether your function works the way you expect it to (there are only `(2 ** sizeof(char)) * (2 ** sizeof(char))` comparisons you need to do.) – wvxvw Jan 01 '18 at 08:35
  • 1
    @wvxvw Did you mean `(2 ** (sizeof(char) * CHAR_BIT))`? – iBug Jan 01 '18 at 09:11
  • @iBug yes, that. – wvxvw Jan 01 '18 at 12:12
  • It may seem beside the point, but: If `*a == *b && *a != 0` then we already know that `*b != 0`, so we shouldn't include that check in the loop. – Elliott Mar 10 '20 at 03:20

3 Answers3

59

The operator solves the problem with numeric overflow that you get with subtraction: if you subtract a large positive number from a negative that is close to INT_MIN, you get a number that cannot be represented as an int, thus causing undefined behavior.

Although version 3 is free from this problem, it utterly lacks readability: it would take some time to understand by someone who has never seen this trick before. <=> operator fixes the readability problem, too.

This is only one problem addressed by the new operator. Section 2.2.3 of Herb Sutter's Consistent comparison paper talks about the use of <=> with other data types of the language where subtraction may produce inconsistent results.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    can you please explain the trick to understanding the 3rd version? It looks like (false/true - true/false) to me – asgs Jan 02 '18 at 19:23
  • 8
    @asgs This trick exploits "duality" of Booleans in C/C++, where `true` and `false` values returned by comparison operators are actually integers `1` and `0`, respectively. [This Q&A](https://stackoverflow.com/a/4609795/335858) provides more details about this trick. – Sergey Kalinichenko Jan 02 '18 at 19:31
43

Here are some cases that subtraction won't work for:

  1. unsigned types.
  2. Operands that cause integer overflow.
  3. User-defined types that don't define operator - (perhaps because it's not meaningful - one may define an order without defining a notion of distance).

I suspect this list is non-exhaustive.

Of course, one can come up with workarounds for at least #1 and #2. But the intent of operator <=> is to encapsulate that ugliness.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 17
    Note that doing 3-way comparisons on strings (not mere `const char*` but an actual string class) is a reasonable operation. Subtracting two strings is not. – Nicol Bolas Dec 31 '17 at 15:24
18

There are some meaningful answers here on the difference, but Herb Sutter in his paper specifically says:

<=> is for type implementers: User code (including generic code) outside the implementation of an operator<=> should almost never invoke an <=> directly (as already discovered as a good practice in other languages);

So even if there was no difference, the point of the operator is different: to aid class writers to generate comparison operators.

The core difference between the subtraction operator and the "spaceship" operator (according to Sutter's proposal) is that overloading operator- gives you a subtraction operator, whereas overloading operator<=>:

  • gives you the 6 core comparison operators (even if you declare the operator as default: no code to write!);
  • declares whether your class is comparable, is sortable, and whether the order is total or partial (strong/weak in Sutter's proposal);
  • allows for heterogeneous comparisons: you can overload it to compare your class to any other type.

Other differences are in the return value: operator<=> would return an enum of a class, the class specifies whether the type is sortable and whether the sort is strong or weak. The return value would convert to -1, 0 or 1 (though Sutter leaves room for the return type to also indicate distance, as strcmp does). In any case, assuming the -1, 0, 1 return value, we'll finally get a true signum function in C++! (signum(x) == x<=>0)

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120