Home > OS >  c 20 default comparison operator and empty base class
c 20 default comparison operator and empty base class

Time:08-26

c 20 default comparison operator is a very convenient feature. But I find it less useful if the class has an empty base class.

The default operator<=> performs lexicographical comparison by successively comparing the base (left-to-right depth-first) and then non-static member (in declaration order) subobjects of T to compute <=>, recursively expanding array members (in order of increasing subscript), and stopping early when a not-equal result is found

According to the standard, the SComparable won't have an operator<=> if base doesn't have an operator<=>. In my opinion it's pointless to define comparison operators for empty classes. So the default comparison operators won't work for classes with an empty base class.

struct base {};

struct SComparable: base {
  int m_n;
  auto operator<=>(SComparable const&) const& = default; // default deleted, clang gives a warning
};

struct SNotComparable: base {
  int m_n;
};

If we are desperate to use default comparison operators and therefore define comparison operators for the empty base class base. The other derived class SNotComparable wrongly becomes comparable because of its empty base class base.

struct base {
  auto operator<=>(base const&) const& = default;
};

struct SComparable: base {
  int m_n;
  auto operator<=>(SComparable const&) const& = default;
};

struct SNotComparable: base { // SNotComparable is wrongly comparable!
  int m_n;
};

So what is the recommended solution for using default comparison operators for classes with an empty base class?

Edit: Some answers recommend to add default comparison operator in the empty base class and explicitly delete comparison operator in non-comparable derived classes.

If we add default comparison operator to a very commonly used empty base class, suddenly all its non-comparable derived classes are all comparable (always return std::strong_ordering::equal). We have to find all these derived non-comparable classes and explicitly delete their comparison operators. If we missed some class and later want to make it comparable but forget to customize its comparison operator (we all make mistakes), we get a wrong result instead of a compile error from not having default comparison operator in the empty base as before. Then why do I use default comparison operator in the first place? I would like to save some efforts instead of introducing more.

struct base {
  auto operator<=>(base const&) const& = default;
};

struct SComparable: base {
  int m_n;
  auto operator<=>(SComparable const&) const& = default;
};

struct SNotComparable1: base {
  int m_n;
  auto operator<=>(SNotComparable1 const&) const& = delete;
};

struct SNotComparableN: base {
  int m_n;
  // oops, forget to delete the comparison operator!
  // if later we want to make this class comparable but forget to customize comparison operator, we get a wrong result instead of a non-comparable compile error.
};

CodePudding user response:

In my opinion it's pointless to define comparison operators for empty classes.

Well, it's clearly not pointless. If what you want to do is default your type's comparisons, that necessarily implies comparing all of your type's subobjects, including the base class subobjects, which requires them to be comparable - even if they're empty.

What you need to do is provide them - just conditionally. The simplest way of doing so is probably to provide a different empty base class:

struct base { /* ... */ };

struct comparable_base : base {
    friend constexpr auto operator==(comparable_base, comparable_base)
        -> bool
    {
        return true;
    }

    friend constexpr auto operator<=>(comparable_base, comparable_base)
        -> std::strong_ordering
    {
        return std::strong_ordering::equal;
    }
};

And then inherit from comparable_base when you want to have comparisons, and base when you don't. That is:

struct SComparable: comparable_base {
  int m_n;
  auto operator<=>(SComparable const&) const& = default;
};

struct SNotComparable: base {
  int m_n;
};

I'm using hidden friend comparisons there just to be able to take the type by value - since it's empty. Could just as easily be a member function too.

CodePudding user response:

what is the recommended solution for using default comparison operators for classes with an empty base class?

The solution is to add the default comparator to the base class and then do what you do in SComparable if you want the added member(s) of SComparable to be included in the comparison - just as with a base class with members.

If you don't want them to be included in the comparison, don't add a default comparator, like you do in SNotComparable - and the base class comparator will be used - again, just like in a base class with members.

If you don't want the base class behavior in SNotComparable and you don't want SNotComparable to be comparable, then delete the comparator, just like you would if the base class had members:

auto operator<=>(SNotComparable const&) const& = delete;
  • Related