{"id":106676,"date":"2022-05-20T07:00:00","date_gmt":"2022-05-20T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106676"},"modified":"2022-05-20T06:29:28","modified_gmt":"2022-05-20T13:29:28","slug":"20220520-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220520-00\/?p=106676","title":{"rendered":"Writing a sort comparison function, part 4: descending sort"},"content":{"rendered":"<p>So far, we&#8217;ve written sort comparison functions on the assumption that we want to sort all keys ascending. But what if you want a mix of ascending and descending?<\/p>\n<p>Unfortunately, <code>std::tuple<\/code> does straight lexicographic ordering, so all the elements are sorted ascending. To get it to do descending sort, you&#8217;ll have to do something clever.<\/p>\n<p>If you just want a straight descending sort across all columns, then you can flip the direction of the top-level comparison. For example, suppose we want to sort by height descending, then width descending:<\/p>\n<pre>void f(std::vector&lt;T&gt;&amp; v)\r\n{\r\n    auto key = [](auto&amp;&amp; t) {\r\n        return std::make_tuple(t.height, t.width);\r\n    };\r\n    std::sort(v.begin(), v.end(), [key](auto&amp;&amp; t, auto&amp;&amp; b) {\r\n        <span style=\"color: blue;\">\/\/ reversed comparison for descending sort<\/span>\r\n        return key(a) <span style=\"color: blue;\">&gt;<\/span> key(b);\r\n    });\r\n}\r\n<\/pre>\n<p>However, it is more likely the case that you want a mix of ascending and descending. For example, you might want descending by height, then ascending by name.<\/p>\n<p>Well, if the thing being sorted descending has a natural way of reversing the order, you can apply that reversal. For example, Boolean values can be <code>!<\/code>&#8216;ed, and signed integers can be negated, assuming they aren&#8217;t the most-negative two&#8217;s complement integer.<\/p>\n<pre>void f(std::vector&lt;T&gt;&amp; v)\r\n{\r\n    auto key = [](auto&amp;&amp; t) {\r\n        <span style=\"color: blue;\">\/\/ ascending by height, descending by width\r\n        return std::make_tuple(t.height, -t.width);<\/span>\r\n    };\r\n    std::sort(v.begin(), v.end(), [key](auto&amp;&amp; t, auto&amp;&amp; b) {\r\n        return key(a) &lt; key(b);\r\n    });\r\n}\r\n<\/pre>\n<p>For unsigned integers, bitwise negation works at reversing the sort order. And in fact, for two&#8217;s complement signed integers, bitwise negation will reverse the sort order as well. So we can use<\/p>\n<pre>void f(std::vector&lt;T&gt;&amp; v)\r\n{\r\n    auto key = [](auto&amp;&amp; t) {\r\n        \/\/ ascending by height, descending by width\r\n        return std::make_tuple(t.height, <span style=\"color: blue;\">~<\/span>t.width);\r\n        \/\/                               ^\r\n    };\r\n    std::sort(v.begin(), v.end(), [key](auto&amp;&amp; t, auto&amp;&amp; b) {\r\n        return key(a) &lt; key(b);\r\n    });\r\n}\r\n<\/pre>\n<p>However, for most types, there is no obvious &#8220;reversal&#8221; transformation on the data. You&#8217;ll have to reverse the comparison itself.<\/p>\n<pre>std::weak_ordering\r\ncompare_3way_for_sorting(T const&amp; a, T const&amp; b)\r\n{\r\n    auto cmp = std::compare_weak_order_fallback(a.name, b.name);\r\n    <span style=\"color: blue;\">\/\/ descending by connector (note that \"a\" and \"b\" are reversed)<\/span>\r\n    if (cmp == 0) cmp = std::compare_weak_order_fallback(<span style=\"color: blue;\">b.GetConnector(), a.GetConnector()<\/span>);\r\n    if (cmp == 0) cmp = std::compare_weak_order_fallback(LookupManufacturingDate(a.part_number),\r\n                                                         LookupManufacturingDate(b.part_number));\r\n    return cmp;\r\n}\r\n<\/pre>\n<p>It&#8217;s important that you leave a comment explaining that the <code>a<\/code> and <code>b<\/code> are reversed: The reversal is easily overlooked, so somebody looking at the code may not realize that we are sorting descending by connector, and somebody copying the code may not realize it either. And for readers who do notice it, you need the comment so that they don&#8217;t think the reversal is a bug and try to &#8220;fix&#8221; it.<\/p>\n<p>I don&#8217;t see any easy way to create a &#8220;reverse-comparison&#8221; wrapper, so I&#8217;ll make my own:<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct descending_compare\r\n{\r\n    descending_compare(T t) : value(std::move(t)) { }\r\n    T value;\r\n\r\n    auto operator&lt;=&gt;(descending_compare const&amp; other) const {\r\n        return std::compare_weak_order_fallback(other.comparand(), comparand());\r\n    }\r\n    std::unwrap_reference_t&lt;T&gt; const&amp; comparand() const {\r\n        return value;\r\n    }\r\n};\r\n<\/pre>\n<p>There are a few tricks here.<\/p>\n<p>First, we implement only the spaceship operator, and let the compiler autogenerate the other comparison operators from it.<\/p>\n<p>Second, we use <code>std::<wbr \/>compare_<wbr \/>weak_<wbr \/>order_<wbr \/>fallback<\/code> to generate the three-way comparison result, even if the wrapped type supports only the two-way comparison operators.<\/p>\n<p>Third, we use <code>unwrap_<wbr \/>reference_t<\/code> to pull the underlying type out of the value, in case the value is a <code>std::reference_<wbr \/>wrapper<\/code>. To get to the innermost value, the compiler would have to apply two user-defined conversions: One from <code>descending_<wbr \/>compare&lt;<wbr \/>reference_<wbr \/>wrapper&lt;T&gt;&gt;<\/code> to <code>reference_<wbr \/>wrapper&lt;T&gt;<\/code>, and then another from <code>reference_<wbr \/>wrapper&lt;T&gt;<\/code> to <code>T<\/code>. But the C++ language rule for conversion chains allows only one user-defined conversion in the chain.<\/p>\n<p>We can wrap a value inside a <code>descending_<wbr \/>compare<\/code> to add a descending key to the sort key:<\/p>\n<pre>void f(std::vector&lt;T&gt;&amp; v)\r\n{\r\n    auto key = [](auto&amp;&amp; t) {\r\n        \/\/ ascending by height, descending by area, descending by name\r\n        return std::make_tuple(\r\n            t.height,\r\n            <span style=\"color: blue;\">descending_compare(t.height * t.width),\r\n            descending_compare(std::ref(t.name))<\/span>);\r\n    };\r\n    std::sort(v.begin(), v.end(), [key](auto&amp;&amp; a, auto&amp;&amp; b) {\r\n        return key(a) &lt; key(b);\r\n    });\r\n}\r\n<\/pre>\n<p>Unfortunately, I still see <code>t.name<\/code>&#8216;s <code>&lt;=&gt;<\/code> operator being called twice, so this still isn&#8217;t as good as I hoped. Maybe somebody can spot what I&#8217;m doing wrong. (I think it has to do with how I declared the spaceship operator. The compiler doesn&#8217;t realize that the <code>descending_compare<\/code> is three-way comparable, so it falls back to doing two two-way comparisons.)<\/p>\n<p><b>Bonus chatter<\/b>: It&#8217;s also easy to overlook the need to wrap the field in a <code>std::ref<\/code> to suppress a copy. We could have a separate <code>descending_field<\/code> wrapper for sorting fields descending.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct descending_field :\r\n    dscending_compare&lt;std::reference_wrapper&lt;T const&gt;&gt;\r\n{\r\n    descending_field(T const&amp; f) :\r\n            descending_compare&lt;std::reference_wrapper&lt;T const&gt;&gt;(std::ref(f)) {}\r\n};\r\n<\/pre>\n<p><b>Bonus bonus chatter<\/b>: I don&#8217;t know why I had to spell out the base class <code>descending_compare&lt;...&gt;<\/code>. I expected the <a title=\"Injected class names: The C++ feature you didn\u2019t even realize that you were using\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220321-00\/?p=106367\"> injected class name<\/a> to allow me to write just <code>descending_compare<\/code>, but it didn&#8217;t work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Flipping the script.<\/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-106676","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Flipping the script.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106676","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=106676"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106676\/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=106676"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106676"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106676"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}