简体   繁体   English

c++20默认比较运算符和空基class

[英]c++20 default comparison operator and empty base class

c++20 default comparison operator is a very convenient feature. c++20默认比较运算符是一个非常方便的功能。 But I find it less useful if the class has an empty base class.但是我发现如果 class 有一个空的基础 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默认操作符<=>通过依次比较基数(从左到右深度优先)和非静态成员(按声明顺序)T的子对象来计算<=>,递归扩展数组成员(在增加下标的顺序),并在发现不相等的结果时提前停止

According to the standard, the SComparable won't have an operator<=> if base doesn't have an operator<=>.根据标准,如果base没有 operator<=>,则SComparable不会有 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.因此,默认比较运算符不适用于具有空基 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 .如果我们不顾一切地使用默认比较运算符,因此为空base class 定义比较运算符。 The other derived class SNotComparable wrongly becomes comparable because of its empty base class base .另一个派生的 class SNotComparable错误地变得可比较,因为它的空基 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?那么对于具有空基 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.编辑:一些答案建议在空基 class 中添加默认比较运算符,并在不可比较的派生类中显式删除比较运算符。

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).如果我们将默认比较运算符添加到一个非常常用的空基 class,突然它所有不可比较的派生类都是可比较的(总是返回 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.如果我们错过了一些 class 并且稍后想要使其具有可比性但忘记自定义其比较运算符(我们都会犯错误),我们会得到错误的结果,而不是像以前那样在空基中没有默认比较运算符而导致编译错误。 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.
};

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.如果您想要做的是默认类型的比较,那必然意味着比较您类型的所有子对象,包括基本的 class 子对象,这要求它们具有可比性——即使它们是空的。

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:这样做的最简单方法可能是提供不同的空基 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.然后在您想要进行comparable_base时从 compare_base 继承,在您不进行比较时从base继承。 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.也可以轻松成为 function 的成员。

what is the recommended solution for using default comparison operators for classes with an empty base class?对于具有空基 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.解决方案是将默认比较器添加到基础 class 中,然后如果您希望添加的SComparable成员包含在比较中,则执行您在SComparable中所做的操作 - 就像带有成员的基础 class 一样。

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.如果您希望将它们包含在比较中,请不要添加默认比较器,就像您在SNotComparable中所做的那样 - 并且将再次使用基本 class 比较器 - 就像在带有成员的基本 class 中一样。

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:如果您希望 SNotComparable 中的基本SNotComparable行为并且不希望SNotComparable具有可比性,则delete比较器,就像基本 class 有成员时一样:

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

I'd like to make a small modification based on @Barry's answer .我想根据@Barry 的回答做一个小的修改。 We could have a generic mix-in class comparable<EmptyBase> that provides comparable operators for any empty base.我们可以有一个通用的混合 class compatible comparable<EmptyBase>为任何空基提供可比较的运算符。 If we want to use default comparison operators for a class derived from empty base class(es), we can simple derive such class from comparable<base> instead of base .如果我们想对从空基类派生的 class 使用默认比较运算符,我们可以简单地从comparable<base>而不是base派生这样的 class 。 It also works for chained empty bases comparable<base1<base2>> .它也适用于链式空基comparable<base1<base2>>

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

template<typename EmptyBase>
struct comparable: EmptyBase {
    static_assert(std::is_empty<EmptyBase>::value);
    friend constexpr auto operator==(comparable, comparable)
        -> bool
    {
        return true;
    }

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

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

struct SNotComparable: base {
  int m_n;
};

struct SComparableNotDefault: base {
  int m_n;
  constexpr bool operator==(SComparableNotDefault const& rhs) const& {
    /* user defined... */
  }
  constexpr auto operator<=>(SComparableNotDefault const& rhs) const& {
    /* user defined... */
  }
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM