{"id":24455,"date":"2019-06-27T14:52:46","date_gmt":"2019-06-27T14:52:46","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cppblog\/?p=24455"},"modified":"2025-06-24T17:20:15","modified_gmt":"2025-06-24T17:20:15","slug":"simplify-your-code-with-rocket-science-c20s-spaceship-operator","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cppblog\/simplify-your-code-with-rocket-science-c20s-spaceship-operator\/","title":{"rendered":"Simplify Your Code With Rocket Science: C++20&#8217;s Spaceship Operator"},"content":{"rendered":"<p><em>This post is part of a <a href=\"https:\/\/devblogs.microsoft.com\/cppblog\/category\/c-qa-series\/\">regular series of posts<\/a> where the C++ product team here at Microsoft and other guests answer questions we have received from customers. The questions can be about anything C++ related: MSVC toolset, the standard language and library, the C++ standards committee, isocpp.org, CppCon, etc. Today&#8217;s post is by Cameron DaCamara.<\/em><\/p>\n<p>C++20 adds a new operator, affectionately dubbed the &#8220;spaceship&#8221; operator: <code>&lt;=&gt;<\/code>. There was a <a href=\"https:\/\/blog.tartanllama.xyz\/spaceship-operator\/\">post<\/a> awhile back by our very own <a href=\"https:\/\/twitter.com\/TartanLlama\">Sy Brand<\/a> detailing some information regarding this new operator along with some conceptual information about what it is and does.\u00a0 The goal of this post is to explore some concrete applications of this strange new operator and its associated counterpart, the <code>operator==<\/code> (yes it has been changed, for the better!), all while providing some guidelines for its use in everyday code.<\/p>\n<h2>Comparisons<\/h2>\n<p>It is not an uncommon thing to see code like the following:<\/p>\n<pre class=\"\">struct IntWrapper {\r\n  int value;\r\n  constexpr IntWrapper(int value): value{value} { }\r\n  bool operator==(const IntWrapper&amp; rhs) const { return value == rhs.value; }\r\n  bool operator!=(const IntWrapper&amp; rhs) const { return !(*this == rhs);    }\r\n  bool operator&lt;(const IntWrapper&amp; rhs)  const { return value &lt; rhs.value;  }\r\n  bool operator&lt;=(const IntWrapper&amp; rhs) const { return !(rhs &lt; *this);     }\r\n  bool operator&gt;(const IntWrapper&amp; rhs)  const { return rhs &lt; *this;        }\r\n  bool operator&gt;=(const IntWrapper&amp; rhs) const { return !(*this &lt; rhs);     }\r\n};\r\n<\/pre>\n<p><em>Note: eagle-eyed readers will notice this is actually even less verbose than it should be in pre-C++20 code because these functions should actually all be nonmember friends, more about that later.<\/em><\/p>\n<p>That is a lot of boilerplate code to write just to make sure that my type is comparable to something of the same type. Well, OK, we deal with it for awhile. Then comes someone who writes this:<\/p>\n<pre class=\"\">constexpr bool is_lt(const IntWrapper&amp; a, const IntWrapper&amp; b) {\r\n  return a &lt; b;\r\n}\r\nint main() {\r\n  static_assert(is_lt(0, 1));\r\n}\r\n<\/pre>\n<p>The first thing you will notice is that this program will not compile.<\/p>\n<p><code>\nerror C3615: constexpr function 'is_lt' cannot result in a constant expression\n<\/code><\/p>\n<p>Ah! The problem is that we forgot <code>constexpr<\/code> on our comparison function, drat! So one goes and adds <code>constexpr<\/code> to all of the comparison operators. A few days later someone goes and adds a <code>is_gt<\/code> helper but notices all of the comparison operators do not have an exception specification and goes through the same tedious process of adding <code>noexcept<\/code> to each of the 5 overloads.<\/p>\n<p>This is where C++20&#8217;s new spaceship operator steps in to help us out. Let&#8217;s see how the original <code>IntWrapper<\/code> can be written in a C++20 world:<\/p>\n<pre class=\"\">#include &lt;compare&gt;\r\nstruct IntWrapper {\r\n  int value;\r\n  constexpr IntWrapper(int value): value{value} { }\r\n  auto operator&lt;=&gt;(const IntWrapper&amp;) const = default;\r\n};\r\n<\/pre>\n<p>The first difference you may notice is the new inclusion of <code>&lt;compare&gt;<\/code>. The <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/header\/compare\"><code>&lt;compare&gt;<\/code><\/a> header is responsible for populating the compiler with all of the comparison category types necessary for the spaceship operator to return a type appropriate for our defaulted function. In the snippet above, the return type <code>auto<\/code> will be deduced to <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/compare\/strong_ordering\"><code>std::strong_ordering<\/code><\/a>.<\/p>\n<p>Not only did we remove 5 superfluous lines, but we don&#8217;t even have to define anything, the compiler does it for us! Our <code>is_lt<\/code> remains unchanged and just works while still being <code>constexpr<\/code> even though we didn&#8217;t explicitly specify that in our defaulted <code>operator&lt;=&gt;<\/code>. That&#8217;s well and good but some people may be scratching their heads as to <em>why<\/em> <code>is_lt<\/code> is allowed to still compile even though it does not even use the spaceship operator at all. Let&#8217;s explore the answer to this question.<\/p>\n<h2>Rewriting Expressions<\/h2>\n<p>In C++20, the compiler is introduced to a new concept referred to &#8220;rewritten&#8221; expressions. The spaceship operator, along with <code>operator==<\/code>, are among the first two candidates subject to rewritten expressions. For a more concrete example of expression rewriting, let us break down the example provided in <code>is_lt<\/code>.<\/p>\n<p>During overload resolution the compiler is going to select from a set of viable candidates, all of which match the operator we are looking for. The candidate gathering process is changed very slightly for the case of relational and equivalency operations where the compiler must also gather special rewritten and synthesized candidates (<a href=\"https:\/\/timsong-cpp.github.io\/cppwp\/over.match#oper-3.4\">[over.match.oper]\/3.4<\/a>).<\/p>\n<p>For our expression <code>a &lt; b<\/code> the standard states that we can search the type of <code>a<\/code> for an <code>operator&lt;=&gt;<\/code> or a namespace scope function <code>operator&lt;=&gt;<\/code> which accepts its type. So the compiler does and it finds that, in fact, <code>a<\/code>&#8216;s type does contain <code>IntWrapper::operator&lt;=&gt;<\/code>. The compiler is then allowed to use that operator and rewrite the expression <code>a &lt; b<\/code> as <code>(a &lt;=&gt; b) &lt; 0<\/code>. That rewritten expression is then used as a candidate for normal overload resolution.<\/p>\n<p>You may find yourself asking why this rewritten expression is valid and correct. The correctness of the expression actually stems from the semantics the spaceship operator provides. The <code>&lt;=&gt;<\/code> is a three-way comparison which implies that you get not just a binary result, but an ordering (in most cases) and if you have an ordering you can express that ordering in terms of any relational operations. A quick example, the expression <code>4 &lt;=&gt; 5<\/code> in C++20 will give you back the result <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/compare\/strong_ordering\"><code>std::strong_ordering::less<\/code><\/a>. The <code>std::strong_ordering::less<\/code> result implies that <code>4<\/code> is not only different from <code>5<\/code> but it is strictly less than that value, this makes applying the operation <code>(4 &lt;=&gt; 5) &lt; 0<\/code> correct and exactly accurate to describe our result.<\/p>\n<p>Using the information above the compiler can take any generalized relational operator (i.e. <code>&lt;<\/code>, <code>&gt;<\/code>, etc.) and rewrite it in terms of the spaceship operator. In the standard the rewritten expression is often referred to as <code>(a &lt;=&gt; b) @ 0<\/code> where the <code>@<\/code> represents any relational operation.<\/p>\n<h2>Synthesizing Expressions<\/h2>\n<p>Readers may have noticed the subtle mention of &#8220;synthesized&#8221; expressions above and they play a part in this operator rewriting process as well. Consider a different predicate function:<\/p>\n<pre class=\"\">constexpr bool is_gt_42(const IntWrapper&amp; a) {\r\n  return 42 &lt; a;\r\n}\r\n<\/pre>\n<p>If we use our original definition for <code>IntWrapper<\/code> this code will not compile.<\/p>\n<p><code>error C2677: binary '&lt;': no global operator found which takes type 'const IntWrapper' (or there is no acceptable conversion)<\/code><\/p>\n<p>This makes sense in pre-C++20 land, and the way to solve this problem would be to add some extra <code>friend<\/code> functions to <code>IntWrapper<\/code> which take a left-hand side of <code>int<\/code>. If you try to build that sample with a C++20 compiler and our C++20 definition of <code>IntWrapper<\/code> you might notice that it, again, &#8220;just works&#8221;\u2014another head scratcher. Let&#8217;s examine why the code above is still allowed to compile in C++20.<\/p>\n<p>During overload resolution the compiler will also gather what the standard refers to as &#8220;synthesized&#8221; candidates, or a rewritten expression with the order of the parameters reversed. In the example above the compiler will try to use the rewritten expression <code>(42 &lt;=&gt; a) &lt; 0<\/code> but it will find that there is no conversion from <code>IntWrapper<\/code> to <code>int<\/code> to satisfy the left-hand side so that rewritten expression is dropped. The compiler also conjures up the &#8220;synthesized&#8221; expression <code>0 &lt; (a &lt;=&gt; 42)<\/code> and finds that there is a conversion from <code>int<\/code> to <code>IntWrapper<\/code> through its converting constructor so this candidate is used.<\/p>\n<p>The goal of the synthesized expressions are to avoid the mess of needing to write the boilerplate of <code>friend<\/code> functions to fill in gaps where your object could be converted from other types. Synthesized expressions are generalized to <code>0 @ (b &lt;=&gt; a)<\/code>.<\/p>\n<h2>More Complex Types<\/h2>\n<p>The compiler-generated spaceship operator doesn&#8217;t stop at single members of classes, it will generate a correct set of comparisons for all of the sub-objects within your types:<\/p>\n<pre class=\"\">struct Basics {\r\n  int i;\r\n  char c;\r\n  float f;\r\n  double d;\r\n  auto operator&lt;=&gt;(const Basics&amp;) const = default;\r\n};\r\n\r\nstruct Arrays {\r\n  int ai[1];\r\n  char ac[2];\r\n  float af[3];\r\n  double ad[2][2];\r\n  auto operator&lt;=&gt;(const Arrays&amp;) const = default;\r\n};\r\n\r\nstruct Bases : Basics, Arrays {\r\n  auto operator&lt;=&gt;(const Bases&amp;) const = default;\r\n};\r\n\r\nint main() {\r\n  constexpr Bases a = { { 0, 'c', 1.f, 1. },\r\n                        { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };\r\n  constexpr Bases b = { { 0, 'c', 1.f, 1. },\r\n                        { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };\r\n  static_assert(a == b);\r\n  static_assert(!(a != b));\r\n  static_assert(!(a &lt; b));\r\n  static_assert(a &lt;= b);\r\n  static_assert(!(a &gt; b));\r\n  static_assert(a &gt;= b);\r\n}\r\n<\/pre>\n<p>The compiler knows how to expand members of classes that are arrays into their lists of sub-objects and compare them recursively. Of course, if you wanted to write the bodies of these functions yourself you still get the benefit of the compiler rewriting expressions for you.<\/p>\n<h2>Looks Like a Duck, Swims Like a Duck, and Quacks Like <code>operator==<\/code><\/h2>\n<p>Some very smart people on the standardization committee noticed that the spaceship operator will always perform a lexicographic comparison of elements no matter what. Unconditionally performing lexicographic comparisons can lead to inefficient generated code with the equality operator in particular.<\/p>\n<p>The canonical example is comparing two strings. If you have the string <code>\"foobar\"<\/code> and you compare it to the string <code>\"foo\"<\/code> using <code>==<\/code> one would expect that operation to be nearly constant. The efficient string comparison algorithm is thus:<\/p>\n<ul>\n<li>First compare the size of the two strings, if the sizes differ return <code>false<\/code>, otherwise<\/li>\n<li>step through each element of the two strings in unison and compare until one differs or the end is reached, return the result.<\/li>\n<\/ul>\n<p>Under spaceship operator rules we need to <em>start<\/em> with the deep comparison on each element first until we find the one that is different. In the our example of <code>\"foobar\"<\/code> and <code>\"foo\"<\/code> only when comparing <code>'b'<\/code> to <code>'\\0'<\/code> do you finally return <code>false<\/code>.<\/p>\n<p>To combat this there was a paper, <a href=\"http:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2019\/p1185r2.html\">P1185R2<\/a> which details a way for the compiler to rewrite and generate <code>operator==<\/code> independently of the spaceship operator. Our <code>IntWrapper<\/code> could be written as follows:<\/p>\n<pre class=\"\">#include &lt;compare&gt;\r\nstruct IntWrapper {\r\n  int value;\r\n  constexpr IntWrapper(int value): value{value} { }\r\n  auto operator&lt;=&gt;(const IntWrapper&amp;) const = default;\r\n  bool operator==(const IntWrapper&amp;) const = default;\r\n};\r\n<\/pre>\n<p>Just one more step&#8230; however, there&#8217;s good news; you don&#8217;t actually <em>need<\/em> to write the code above, because simply writing <code>auto operator&lt;=&gt;(const IntWrapper&amp;) const = default<\/code> is enough for the compiler to <em>implicitly<\/em> generate the separate\u2014and more efficient\u2014<code>operator==<\/code> for you!<\/p>\n<p>The compiler applies a slightly altered &#8220;rewrite&#8221; rule specific to <code>==<\/code> and <code>!=<\/code> wherein these operators are rewritten in terms of <code>operator==<\/code> and <em>not<\/em> <code>operator&lt;=&gt;<\/code>. This means that <code>!=<\/code> also benefits from the optimization, too.<\/p>\n<h2>Old Code Won&#8217;t Break<\/h2>\n<p>At this point you might be thinking, OK if the compiler is allowed to perform this operator rewriting business what happens when I try to outsmart the compiler:<\/p>\n<pre class=\"\">struct IntWrapper {\r\n  int value;\r\n  constexpr IntWrapper(int value): value{value} { }\r\n  auto operator&lt;=&gt;(const IntWrapper&amp;) const = default;\r\n  bool operator&lt;(const IntWrapper&amp; rhs) const { return value &lt; rhs.value; }\r\n};\r\nconstexpr bool is_lt(const IntWrapper&amp; a, const IntWrapper&amp; b) {\r\n  return a &lt; b;\r\n}\r\n<\/pre>\n<p>The answer here is, you didn&#8217;t. The overload resolution model in C++ has this arena where all of the candidates do battle, and in this specific battle we have 3 candidates:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li><code>IntWrapper::operator&lt;(const IntWrapper&amp; a, const IntWrapper&amp; b)<\/code><\/li>\n<li><code>IntWrapper::operator&lt;=&gt;(const IntWrapper&amp; a, const IntWrapper&amp; b)<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>(rewritten)<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li><code>IntWrapper::operator&lt;=&gt;(const IntWrapper&amp; b, const IntWrapper&amp; a)<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>(synthesized)<\/p>\n<p>If we accepted the overload resolution rules in C++17 the result of that call would have been ambiguous, but the C++20 overload resolution rules were changed to allow the compiler to resolve this situation to the most logical overload.<\/p>\n<p>There is a phase of overload resolution where the compiler must perform a series tiebreakers. In C++20, there is a new tiebreaker that states we must prefer overloads that are not rewritten or synthesized, this makes our overload <code>IntWrapper::operator&lt;<\/code> the best candidate and resolves the ambiguity. This same machinery prevents synthesized candidates from stomping on regular rewritten expressions.<\/p>\n<h2>Closing Thoughts<\/h2>\n<p>The spaceship operator is a welcomed addition to C++ and it is one of the features that will simplify and help you to write <em>less<\/em> code, and, sometimes, less is more. So buckle up with C++20&#8217;s <a href=\"https:\/\/xkcd.com\/1133\/\">spaceship<\/a> operator!<\/p>\n<p>We urge you to go out and try the spaceship operator, it&#8217;s available right now in <a href=\"https:\/\/visualstudio.microsoft.com\/downloads\/\">Visual Studio 2019<\/a> under <code>\/std:c++latest<\/code>! As a note, the changes introduced through <a href=\"http:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2019\/p1185r2.html\">P1185R2<\/a> will be available in Visual Studio 2019 version 16.2. Please keep in mind that the spaceship operator is part of C++20 and is subject to some changes up until such a time that C++20 is finalized.<\/p>\n<p>As always, we welcome your feedback. Feel free to send any comments through e-mail at <a href=\"mailto:visualcpp@microsoft.com\">visualcpp@microsoft.com<\/a>, through <a href=\"https:\/\/twitter.com\/visualc\">Twitter @visualc<\/a>, or Facebook at <a href=\"https:\/\/www.facebook.com\/Microsoft-Visual-Cpp-222043184527264\/\">Microsoft Visual Cpp<\/a>. Also, feel free to follow me on Twitter <a href=\"https:\/\/twitter.com\/starfreakclone\">@starfreakclone<\/a>.<\/p>\n<p>If you encounter other problems with MSVC in VS 2019 please let us know via the <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/ide\/how-to-report-a-problem-with-visual-studio?view=vs-2019\">Report a Problem<\/a> option, either from the installer or the Visual Studio IDE itself. For suggestions or bug reports, let us know through <a href=\"https:\/\/developercommunity.visualstudio.com\/\">DevComm.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post is part of a regular series of posts where the C++ product team here at Microsoft and other guests answer questions we have received from customers. The questions can be about anything C++ related: MSVC toolset, the standard language and library, the C++ standards committee, isocpp.org, CppCon, etc. Today&#8217;s post is by Cameron [&hellip;]<\/p>\n","protected":false},"author":39620,"featured_media":35994,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[862,512],"tags":[140,100,2064,2065,36],"class_list":["post-24455","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c-qa-series","category-general-cpp-series","tag-c","tag-c-language","tag-c20","tag-spaceship-operator","tag-vc"],"acf":[],"blog_post_summary":"<p>This post is part of a regular series of posts where the C++ product team here at Microsoft and other guests answer questions we have received from customers. The questions can be about anything C++ related: MSVC toolset, the standard language and library, the C++ standards committee, isocpp.org, CppCon, etc. Today&#8217;s post is by Cameron [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/24455","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/users\/39620"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/comments?post=24455"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/24455\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media\/35994"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media?parent=24455"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/categories?post=24455"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/tags?post=24455"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}