{"id":106661,"date":"2022-05-16T07:06:52","date_gmt":"2022-05-16T14:06:52","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106661"},"modified":"2022-05-16T07:06:52","modified_gmt":"2022-05-16T14:06:52","slug":"20220516-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220516-52\/?p=106661","title":{"rendered":"How can I synthesize a C++20 three-way comparison from two-way comparisons?"},"content":{"rendered":"<p>The C++20 three-way comparison operator <tt>&lt;=&gt;<\/tt> (commonly nicknamed the <i>spaceship operator<\/i> due to its <a href=\"https:\/\/en.wikipedia.org\/wiki\/Unidentified_flying_object\"> appearance<\/a>) compares two items and describes the result. It&#8217;s called the three-way comparison because there are five possible results: <i>less<\/i>, <i>equal<\/i>, <i>equivalent<\/i>, <i>greater<\/i>, and <i>unordered<\/i>.<\/p>\n<p>Yeah, the name is kind of weird.<\/p>\n<p>It&#8217;s called the three-way comparison because in other languages, the equivalent operator has three possible results: <i>less<\/i>, <i>equal<\/i>, and <i>greater<\/i>. C++20 expands the set of possible results but kept the old name. (<a title=\"Rule of three\" href=\"https:\/\/en.wikipedia.org\/wiki\/Rule_of_three_(C%2B%2B_programming)\">Sound familiar<\/a>?)<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Ordering<\/th>\n<th colspan=\"5\">Results<\/th>\n<\/tr>\n<tr>\n<td>strong ordering<\/td>\n<td>less<\/td>\n<td>equal<\/td>\n<td>equivalent<\/td>\n<td>greater<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>weak ordering<\/td>\n<td>less<\/td>\n<td colspan=\"2\">equivalent<\/td>\n<td>greater<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>partial ordering<\/td>\n<td>less<\/td>\n<td colspan=\"2\">equivalent<\/td>\n<td>greater<\/td>\n<td>unordered<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each of the orderings can convert to the one below it, using the conversions given by the chart.<\/p>\n<p>The strong ordering distinguishes between items being <i>equal<\/i> (identical and interchangeable) and <i>equivalent<\/i> (not interchangeable but close enough for some purpose). For example, two instances of the same string <code>\"hello\"<\/code> are equal, in that they represent the same string and are fully interchangeable. On the other hand, two people with the same security clearance are <i>equivalent<\/i> from a security perspective (they have access to the same things), but they are not <i>equal<\/i> (they are nevertheless different people).<\/p>\n<p>When sorting, you are usually interested in equivalence, but when searching you might be interested in equality. (I&#8217;m looking for Bob, not just anybody with the same security clearance as Bob.)<\/p>\n<p>Suppose you have an object from a class library that predates C++20 and doesn&#8217;t support three-way comparison. You want your code to be able to take advantage of the three-way comparison should the library be updated but fall back to two-way comparison in the meantime. In other words, you want to take advantage of three-way comparison if available.<\/p>\n<p>Fortunately, you don&#8217;t have to write all that SFINAE nonsense, because somebody else has done it for you: <code>std::tuple<\/code>.<\/p>\n<p>Tuples have the bonus property of supporting the three-way comparison operator, even if the underlying types do not. In the case where they do not, they will synthesize a three-way comparison from the two-way comparisons.<\/p>\n<table class=\"cp3\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>if <code>a &lt; b<\/code><\/td>\n<td>return <code>less<\/code><\/td>\n<\/tr>\n<tr>\n<td>else if <code>a &gt; b<\/code><\/td>\n<td>return <code>greater<\/code><\/td>\n<\/tr>\n<tr>\n<td>otherwise<\/td>\n<td>return <code>equivalent<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>So we can just wrap the objects inside a <code>std::tuple<\/code> and compare the tuples. To avoid unnecessary copies, we can wrap them as references, or use <code>forward_<wbr \/>as_<wbr \/>tuple<\/code> which <!-- backref: What's up with <CODE>std::piecewise_construct<\/CODE> and <CODE>std::forward_as_tuple<\/CODE>? --> always uses references.<\/p>\n<pre>std::weak_ordering\r\ncompare_3way_via_tuple(T const&amp; a, T const&amp; b)\r\n{\r\n    return std::forward_as_tuple(a) &lt;=&gt;\r\n           std::forward_as_tuple(b);\r\n}\r\n<\/pre>\n<p>It turns out that there&#8217;s already a pre-made function that does something very similar: <code>std::<wbr \/>compare_<wbr \/>weak_<wbr \/>order_<wbr \/>fallback<\/code> also synthesize a missing three-way comparison, but it uses a different algorithm from tuples:<\/p>\n<table class=\"cp3\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>if <code>a == b<\/code><\/td>\n<td>return <code>equivalent<\/code><\/td>\n<\/tr>\n<tr>\n<td>else if <code>a &lt; b<\/code><\/td>\n<td>return <code>less<\/code><\/td>\n<\/tr>\n<tr>\n<td>otherwise<\/td>\n<td>return <code>greater<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Tuples use a different algorithm from <code>std::<wbr \/>compare_<wbr \/>weak_<wbr \/>order_<wbr \/>fallback<\/code>. Which one is better? Why are they different?<\/p>\n<p>I suspect that tuples use a different algorithm because tuple ordering comes from C++11, which predates three-way comparison. Back in those days, the comparison operators was used mostly for sorting and other ordered-sequence type algorithms. And those algorithms require only that the objects support the <code>&lt;<\/code> operator. Therefore, tuples have to make do with only the <code>&lt;<\/code> operator.<\/p>\n<p>On the other hand, <code>std::<wbr \/>compare_<wbr \/>weak_<wbr \/>order_<wbr \/>fallback<\/code> was born into the world of three-way comparisons, so it has more liberty to take dependencies on things beyond just the <code>&lt;<\/code> operator.<\/p>\n<p>If you know that the underlying object supports <code>==<\/code>, then my guess is that <code>std::<wbr \/>compare_<wbr \/>weak_<wbr \/>order_<wbr \/>fallback<\/code> is better, because <code>==<\/code> testing tends to be faster than <code>&lt;<\/code> testing. For example, comparing two strings for equality can short-circuit if the strings are different lengths. This shortcut is not available for less-than comparison.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Multiple ways of synthesizing a comparison.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-106661","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Multiple ways of synthesizing a comparison.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106661","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=106661"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106661\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=106661"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106661"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106661"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}