【问题标题】:Comparison operators in inherited classes继承类中的比较运算符
【发布时间】:2018-05-11 13:09:03
【问题描述】:

我定义了一个IntWrapper 类,如下所示:

struct IntWrapper
{
protected:
  int value;

public:
  explicit IntWrapper() = default;
  explicit IntWrapper(const int value) : value(value) {}

  bool operator< (const IntWrapper rhs) const { return value <  rhs.value; }
  bool operator> (const IntWrapper rhs) const { return value >  rhs.value; }
  bool operator<=(const IntWrapper rhs) const { return value <= rhs.value; }
  bool operator>=(const IntWrapper rhs) const { return value >= rhs.value; }
  bool operator==(const IntWrapper rhs) const { return value == rhs.value; }

  explicit operator int() const { return value; }
};

以及继承自 IntWrapper 的类 FooBar

struct Foo: IntWrapper
{
  using IntWrapper::IntWrapper;
};

struct Bar: IntWrapper
{
  using IntWrapper::IntWrapper;
};

我只想比较相同类型的对象。换句话说,我希望下面的代码给出编译错误,而不是将 foobar 转换为 IntWrapper

const Foo foo(1);
const Bar bar(2);
bool b = foo >= bar;

由于我有许多其他对象,例如 FooBar,有什么方法可以实现我的结果,将所有比较运算符保留在 IntWrapper 中?

【问题讨论】:

  • 只是您想要禁止的比较吗?您是否可以向 IntWrapper 添加“无用”模板参数,这会使它们成为不同的基础,因为您最终会拥有:struct Foo : IntWrapper&lt;Foo&gt;
  • 像 Bar 和 Foo 这样的声音不是 IntWrapper,但应该包含 IntWrapper。
  • @UKMonkey 如果你这样做,你必须为每个类中的运算符实现接口(即使它们只是转发到 intwrapper)我假设这是 OP 试图避免的?

标签: c++ c++11


【解决方案1】:

您可以向您的IntWrapper 添加一个虚拟模板,以使比较运算符仅适用于相同类型的IntWrapper

template<class>
struct IntWrapper
{ /* same code */ };

struct Foo : IntWrapper<Foo> { using IntWrapper::IntWrapper; };
struct Bar : IntWrapper<Bar> { using IntWrapper::IntWrapper; };

int main()
{
    const Foo foo(1);
    const Bar bar(2);
    //bool b = foo >= bar; // error: no match for 'operator>=' (operand types are 'const Foo' and 'const Bar')
}

live demo

【讨论】:

  • 这不是解决方案。您阻止了来自Foo 的任何继承。
  • @liliscent 是的,我也发现这可能是一个很好的解决方案。你能再解释一下你的意思吗?
  • 如果您有 2 个不同的类派生自 Foo,它们可以在您的代码中进行比较,这不是 OP 的预期结果。这里的问题是您需要跟踪最基类中最派生的那个,一个完整的解决方案将使所有内容都成为模板。
  • 假设有不止一个继承级别。我的理解是,这是一个通用的类型安全的 int 类型,当你需要一种新的类型安全的 int 并且继承“树”停在那里时,就可以继承它。
  • 如果您不需要多个继承级别,请使用final 并完成它
【解决方案2】:

我只想比较相同类型的对象。

如果您希望两个 IntWrapper 实例具有可比性,但不是从 IntWrapper 继承的类的实例,您实际上是在说您并不真的希望这些类从 IntWrapper 继承。

  • @YSC suggested 你使用 CRTP 模式阻止了这种继承,这样每个类都继承了它“自己的”IntWrapper。这样可以防止代码重复。
  • 您可以更改比较运算符,使其检查是否属于同一 IntWrapper 子类的元素。
  • 您可以将可比性归结为 IntWrapper 的子类,Foo 和 Bar 不继承该子类。

而且可能还有更多方法可以实现这一点。但是,再一次,这听起来像是一个有问题的设计。

【讨论】:

    【解决方案3】:

    为这些运算符使用模板并将它们声明为朋友。

    template <typename T, 
              std::enable_if_t<std::is_base_of_v<IntWrapper, T>, int> = 0> 
    friend bool operator< (T lhs, T rhs) { return lhs.value <  rhs.value; }
    

    现在如果参数的真实类型不同,T 就无法正确推导出来。


    注意我不使用const T,因为top-level const of function parameter is ignored

    【讨论】:

    • 虽然这应该可行,但其意图对于未来的维护者/开发者来说可能并不明显,而且我会担心由于代码与此冲突而导致的错误。
    • @einpoklum 这就是 cmets 所做的。
    • std::enable_if_t&lt;std::is_base_of_v&lt;...&gt;&gt; 的意图非常明确。如果您需要额外的,文档“不得参与重载决议,除非......”就像标准描述它的受约束模板一样
    • @Caleth:这绝对不是明确的意图,因为您必须: 1. 请注意代码存在。 2. 了解一些模板元编程来阅读它 3. 知道不会发生隐式转换。
    • @einpoklum 1. 它在类的声明中(参见friend)。 2. 它包含“enable if is base of” 3. 从什么到什么的隐式转换?自身的推断类型?
    猜你喜欢
    • 2012-12-02
    • 2021-09-05
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    相关资源
    最近更新 更多