Home > Software engineering >  Constness and comparison concepts in generic code
Constness and comparison concepts in generic code

Time:03-06

Taking a look at cppreference

template<class T, class U, class Cat = std::partial_ordering>
concept three_way_comparable_with =
  std::three_way_comparable<T, Cat> &&
  std::three_way_comparable<U, Cat> &&
  std::common_reference_with<
    const std::remove_reference_t<T>&,
    const std::remove_reference_t<U>&> &&
  std::three_way_comparable<
    std::common_reference_t<
      const std::remove_reference_t<T>&,
      const std::remove_reference_t<U>&>, Cat> &&
  __WeaklyEqualityComparableWith<T, U> &&
  __PartiallyOrderedWith<T, U> &&
  requires(const std::remove_reference_t<T>& t,
           const std::remove_reference_t<U>& u) {
    { t <=> u } -> __ComparesAs<Cat>;
    { u <=> t } -> __ComparesAs<Cat>;
  };

In all comparison concepts, the operands (t and u here) are always const-qualified references.

This means that if I have an "unorthodox" class

struct S {
    int i;
    auto operator<=>(const S&) const = default;
    void* operator<=>(S&) = delete;
};

then S models std::three_way_comparable but the following fails

S a, b;
a <=> b;

Does this mean that I must enforce constness for comparisons in any generic code?

template<typename T, typename U>
requires std::three_way_comparable_with<T, U>
auto foo(T&& t, U&& u)
{
    // if(t <=> u > 0)  // wrong!
    if(static_cast<const std::remove_reference_t<T>&>(t) <=>
       static_cast<const std::remove_reference_t<U>&>(u) > 0)
        return bar(std::forward<T>(t));
    else
        return baz(std::forward<U>(u));
}

CodePudding user response:

As hinted by @StoryTeller in the comments:

According to the "implicit expression variations" in [concepts.equality]/6, because the expression t <=> u is non-modifying (the operands are const lvalue references), variations of the expression expecting non-const lvalue references instead of const lvalue references are implicitly also required. However, it is unspecified whether these requirements are validated.

I don't see any exceptions to this rule for std::three_way_comparable_with in [cmp.concept].

Your type S violates this additional requirement because the variation

  requires(std::remove_reference_t<T>& t,
           std::remove_reference_t<U>& u) {
    { t <=> u } -> __ComparesAs<Cat>;
    { u <=> t } -> __ComparesAs<Cat>;
  };

is not satisfied. It therefore doesn't model std::three_way_comparable_with.

There is even a very similar example under the referenced clause.

  • Related