What Is the <=> ("Spaceship", Three-Way Comparison) Operator in C++

What is the = (spaceship, three-way comparison) operator in C++?

On 2017-11-11, the ISO C++ committee adopted Herb Sutter's proposal for the <=> "spaceship" three-way comparison operator as one of the new features that were added to C++20. In the paper titled Consistent comparison Sutter, Maurer and Brown demonstrate the concepts of the new design. For an overview of the proposal, here's an excerpt from the article:

The expression a <=> b returns an object that compares <0 if a <
b
, compares >0 if a > b, and compares ==0 if a and b are
equal/equivalent.

Common case: To write all comparisons for your type X with type Y, with memberwise semantics, just write:

auto X::operator<=>(const Y&) =default;

Advanced cases: To write all comparisons for your type X with type Y, just write operator<=> that takes a Y, can use
=default to get memberwise semantics if desired, and returns the
appropriate category type:

  • Return an _ordering if your type naturally supports <, and we’ll efficiently generate symmetric <, >, <=, >=, ==, and
    !=; otherwise return an _equality, and we’ll efficiently generate
    symmetric == and !=.
  • Return strong_ if for your type a == b implies f(a) == f(b) (substitutability, where f reads only comparison-salient state that
    is accessible using the public const members), otherwise return
    weak_.

Comparison Categories

Five comparison categories are defined as std:: types, each having the following predefined values:

+--------------------------------------------------------------------+
| | Numeric values | Non-numeric |
| Category +-----------------------------------+ |
| | -1 | 0 | +1 | values |
+------------------+------+------------+---------------+-------------+
| strong_ordering | less | equal | greater | |
| weak_ordering | less | equivalent | greater | |
| partial_ordering | less | equivalent | greater | unordered |
| strong_equality | | equal | nonequal | |
| weak_equality | | equivalent | nonequivalent | |
+------------------+------+------------+---------------+-------------+

Implicit conversions between these types are defined as follows:

  • strong_ordering with values {less, equal, greater} implicitly converts to:

    • weak_ordering with values {less, equivalent, greater}
    • partial_ordering with values {less, equivalent, greater}
    • strong_equality with values {unequal, equal, unequal}
    • weak_equality with values {nonequivalent, equivalent, nonequivalent}
  • weak_ordering with values {less, equivalent, greater} implicitly converts to:

    • partial_ordering with values {less, equivalent, greater}
    • weak_equality with values {nonequivalent, equivalent, nonequivalent}
  • partial_ordering with values {less, equivalent, greater, unordered} implicitly converts to:

    • weak_equality with values {nonequivalent, equivalent, nonequivalent, nonequivalent}
  • strong_equality with values {equal, unequal} implicitly converts to:

    • weak_equality with values {equivalent, nonequivalent}

Three-way comparison

The<=>token is introduced. The character sequence<=>tokenizes to<= >, in old source code. For example,X<&Y::operator<=>needs to add a space to retain its meaning.

The overloadable operator<=>is a three-way comparison function and has precedence higher than< and lower than<<. It returns a type that can be compared against literal0but other return types are allowed such as to support expression templates. All<=>operators defined in the language and in the standard library return one of the 5 aforementionedstd::comparison category types.

For language types, the following built-in<=>same-type comparisons are provided. All are constexpr, except where noted otherwise. These comparisons cannot be invoked heterogeneously using scalar promotions/conversions.

  • Forbool, integral, and pointer types,<=>returnsstrong_ordering.
  • For pointer types, the different cv-qualifications and derived-to-base conversions are allowed to invoke a homogeneous built-in<=>, and there are built-in heterogeneousoperator<=>(T*, nullptr_t). Only comparisons of pointers to the same object/allocation are constant expressions.
  • For fundamental floating point types,<=> returnspartial_ordering, and can be invoked heterogeneously by widening arguments to a larger floating point type.
  • For enumerations,<=> returns the same as the enumeration's underlying type's<=>.
  • Fornullptr_t,<=> returnsstrong_orderingand always yieldsequal.
  • For copyable arrays,T[N] <=> T[N]returns the same type asT's<=>and performs lexicographical elementwise comparison. There is no<=>for other arrays.
  • Forvoidthere is no<=>.

To better understand the inner workings of this operator, please read the original paper. This is just what I've found out using search engines.

How to use three-way comparison (spaceship op) to implement operator== between different types?

The premise of the question is wrong. You don't use the three-way comparison operator (<=>) to implement ==: you use == to implement ==:

bool operator==(type_a a, type_b b) {
return a.member == b.member;
}

The source of confusion is that there is one exception to this rule: if a type declares a defaulted <=> then it also declares a defaulted ==:

struct type_c {
int member;
auto operator<=>(type_c const&) const = default;
};

That declaration is equivalent to having written:

struct type_c {
int member;
bool operator==(type_c const&) const = default;
auto operator<=>(type_c const&) const = default;
};

But it's not the <=> that gives you ==: it's still the ==, and only ==, that gives you ==.

Why should I use the three-way comparison operator ( = ) instead of the two-way comparison operators? Does this have an advantage?

It makes it possible to determine the ordering in one operation.

The other operators require two comparisons.

Summary of the other operators:

  • If a == b is false, you don't know whether a < b or a > b
  • If a != b is true, you don't know whether a < b or a > b
  • If a < b is false, you don't know whether a == b or a > b
  • If a > b is false, you don't know whether a == b or a < b
  • If a <= b is true, you don't know whether a == b or a < b
  • If a >= b is true, you don't know whether a == b or a > b

A neat side effect is that all the other operators can be implemented in terms of <=>, and a compiler can generate them for you.

Another side effect is that people might be confused by the use of <=> as the equivalence arrow in mathematics, which it has been pretty much since typewriters got those three symbols.

(I'm personally pretty miffed by how a <=> b is "truthy" if and only if a and b are not equivalent.)

Checking for three-way-comparison operator support at compile time

This is what feature-test macros are for. There is a standing document that defines all the macros and their values; those are the macros and values that you check for, that all vendors agree to abide by.

Three-way-comparison specifically is a bit tricky because this is a feature that requires both language and library support. There is a language-level feature test macro, but it isn't intended for you (the user), it's intended for the standard library author to conditionally provide that functionality.

So what you really have to do this is this:

#if __has_include(<compare>)
# include <compare>
# if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907
# define SPACESHIP_OPERATOR_IS_SUPPORTED 1
# endif
#endif

And now in the rest of your code you can check #ifdef SPACESHIP_OPERATOR_IS_SUPPORTED to conditionally provide <=>:

#ifdef SPACESHIP_OPERATOR_IS_SUPPORTED
bool operator==(const thing<N> &) const = default;
std::strong_ordering operator<=>(const thing<N> &) const = default;

template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> std::strong_ordering operator<=>(const thing<R> &) const = delete;
#else
bool operator==(const thing<N> &) const { return true; }
bool operator!=(const thing<N> &) const { return false; }
bool operator< (const thing<N> &) const { return false; }
bool operator> (const thing<N> &) const { return false; }
bool operator<=(const thing<N> &) const { return true; }
bool operator>=(const thing<N> &) const { return true; }

template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> bool operator!=(const thing<R> &) const = delete;
template <int R> bool operator< (const thing<R> &) const = delete;
template <int R> bool operator> (const thing<R> &) const = delete;
template <int R> bool operator<=(const thing<R> &) const = delete;
template <int R> bool operator>=(const thing<R> &) const = delete;
#endif

You don't need to provide both defaulted <=> and all the relational operators. That's why we have <=>: so you can write <=> by itself. You still need to provide operator== but only because you're doing something special in needing to delete <=>.

Three-way comparison operator member vs non-member implementation

Here's an illustrative (though not necessarily practical) example:

struct A {
int i;
};

struct B {
B(A a) : i(a.i) { }

int i;
};

strong_ordering operator<=>(B const& lhs, B const& rhs) {
return lhs.i <=> rhs.i;
}

A{2} == A{2}; // okay, true
A{2} < A{1}; // okay, false

We find the candidate taking two Bs at global scope, and it's viable, so we convert both arguments and use it. If that operator was either a member function or a non-member friend declared within the class, name lookup wouldn't have found it.


Note that in the OP, <=> is declared as a non-member friend within the class. This means that name lookup would not find it for 42 == string_view("42"), since neither of those arguments is a foo. You would need to add a normal non-member declaration to make it visible for such lookup.

Is the three-way comparison operator always efficient?

Would define operator>(a,b) as a<=>b > 0 not lead to large overhead?

It would lead to some overhead. The magnitude of the overhead is relative, though - in situations when costs of running comparisons are negligible in relation to the rest of the program, reducing code duplication by implementing one operator instead of five may be an acceptable trade-off.

However, the proposal does not suggest removing other comparison operators in favor of <=>: if you want to overload other comparison operators, you are free to do it:

Be general: Don’t restrict what is inherent. Don’t arbitrarily restrict a complete set of uses. Avoid special cases and partial features. – For example, this paper supports all seven comparison operators and operations, including adding three-way comparison via <=>. It also supports all five major comparison categories, including partial orders.

How to use spaceship = operator with strcmp style function?

From cppreference:

The three-way comparison operator expressions have the form

lhs <=> rhs

The expression returns an object such that

  • (a <=> b) < 0 if lhs < rhs
  • (a <=> b) > 0 if lhs > rhs
  • (a <=> b) == 0 if lhs and rhs are equal/equivalent.

So you can just simply do

auto operator <=> (catxx& a, catxx& b)
{
return compare(a.ct, b.ct) <=> 0;
}

Since the operands are integral type, the operator yields a prvalue of type std::strong_ordering.

Reference — What does this symbol mean in PHP?

Incrementing / Decrementing Operators

++ increment operator

-- decrement operator

Example    Name              Effect
---------------------------------------------------------------------
++$a Pre-increment Increments $a by one, then returns $a.
$a++ Post-increment Returns $a, then increments $a by one.
--$a Pre-decrement Decrements $a by one, then returns $a.
$a-- Post-decrement Returns $a, then decrements $a by one.

These can go before or after the variable.

If put before the variable, the increment/decrement operation is done to the variable first then the result is returned. If put after the variable, the variable is first returned, then the increment/decrement operation is done.

For example:

$apples = 10;
for ($i = 0; $i < 10; ++$i) {
echo 'I have ' . $apples-- . " apples. I just ate one.\n";
}

Live example

In the case above ++$i is used, since it is faster. $i++ would have the same results.

Pre-increment is a little bit faster because it really increments the variable and after that 'returns' the result. Post-increment creates a special variable, copies there the value of the first variable and only after the first variable is used, replaces its value with second's.

However, you must use $apples--, since first, you want to display the current number of apples, and then you want to subtract one from it.

You can also increment letters in PHP:

$i = "a";
while ($i < "c") {
echo $i++;
}

Once z is reached aa is next, and so on.

Note that character variables can be incremented but not decremented and even so only plain ASCII characters (a-z and A-Z) are supported.


Stack Overflow Posts:

  • Understanding Incrementing

How do I compare two DateTime objects in PHP 5.2.8?

The following seems to confirm that there are comparison operators for the DateTime class:

dev:~# php
<?php
date_default_timezone_set('Europe/London');

$d1 = new DateTime('2008-08-03 14:52:10');
$d2 = new DateTime('2008-01-03 11:11:10');
var_dump($d1 == $d2);
var_dump($d1 > $d2);
var_dump($d1 < $d2);
?>
bool(false)
bool(true)
bool(false)
dev:~# php -v
PHP 5.2.6-1+lenny3 with Suhosin-Patch 0.9.6.2 (cli) (built: Apr 26 2009 20:09:03)
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
dev:~#


Related Topics



Leave a reply



Submit