P0515R3: Consistent comparison

Design

The proposal proposes comparison categories as types in std: partial_ordering, weak_ordering, strong_ordering, weak_equality, and strong_equality. (weak_equality and strong_equality are removed in P1959R0.) Making them types enables using the type system to guide the generation of the correct appropriate related comparisons.

  • partial_ordering admits all relational operators (==, !=, <, <=, >, >=), equivalent values can be distinguishable and admits incomparable values. It has 4 valid values: less, equivalent, greater, and unordered.
  • weak_ordering admits all relational operators (==, !=, <, <=, >, >=), equivalent values can be distinguishable and doesn't admit incomparable values. It has 3 valid values: less, equivalent, and greater. It's convertible to partial_ordering.
  • strong_ordering admits all relational operators (==, !=, <, <=, >, >=), equivalent values are indistinguishable and don't admit incomparable values. It has 3 valid values: less, equal, and greater. It's convertible to weak_ordering and partial_ordering.

The class template std::common_comparison_category provides an alias for the strongest comparison category to which all of the template arguments Ts... can be converted. If there is a T that is not a comparison category type, the alias is void.

template <typename... Ts> struct common_comparison_category {};

template <typename... Ts>
using common_comparison_category_t = common_comparison_category<Ts...>::type;

static_assert(std::same_as<
              std::common_comparison_category_t<
                  std::partial_ordering, std::weak_ordering,
                  std::strong_ordering>,
              std::partial_ordering>);

operator<=> is a generalized comparison function and has precedence higher than < and lower than <<. It returns a type that can be compared against the literal 0. There is no restriction on parameter types. Generic code can deduce a type's ordering from the return type of <=>. It eliminates redundant comparisons in the implementation, as shown in the following example:

struct pair_t {
  std::uint8_t a;
  std::uint8_t b;

#if __cplusplus >= 202002L
  auto operator<=>(const pair_t &other) const {
    if (auto cmp = a <=> other.a; cmp != 0) {
      return cmp;
    }
    return b <=> other.b;
  }
#else
  bool operator<(const pair_t &other) const {
    // The implementation needs to perform 2 comparisons for `a`
    if (a < other.a) {
      return true;
    } else if (a > other.a) {
      return false;
    }
    return b < other.b;
  }
#endif
};
  • For fundamental bool, integral, and pointer types, <=> returns strong_ordering.
  • For fundamental floating point types, <=> returns partial_ordering.
  • For enumerations, <=> returns the same as the enumeration's underlying type's <=>.
  • For nullptr_t, <=> returns strong_ordering::equal.
  • For each STL type that supports comparison, <=> returns the appropriate comparison category type and returns consistent results with the existing comparison operators.