Implementing polymorphic operator == () in C ++ in an idiomatic way

I think what I need is like a pure virtual function, except that the base class has an implementation whose derived classes should not by default (and this rule should extend). The opposite final

?

I have several types derived from a common base. The base is a useful type, and some derived types are derived from other derived types. I will only work with references to the base class, but I need virtual operator==()

one that sees this and names the manual comparisons appropriate for each situation. A 2D vtable type for operator==()

.

It is important that implementations do not propagate to derived classes, because if this happens by accident it can lead to a mapping of incompatible types that end up in the base class implementation where they are compatible types, and this can lead to false positives.

I want specific functionally equivalent cases to compare in the same way, even though they have been expressed in different classes. I expect the problem to spread to other operations as well, and perhaps my use operator==()

here is not acceptable.

I am not claiming to know C ++. I just hacked C trying to be idiomatic.

Here's what I've developed so far:

class base;
class foo;
class bar;
class baz;

#define COMPARE public: \
  virtual bool equal(base const &p) const; \
  virtual bool equal(foo const &p) const; \
  virtual bool equal(bar const &p) const; \
  virtual bool equal(baz const &p) const; \
  virtual bool operator==(base const &p) const { return p.equal(*this); }

class base {
  int a_;

 public:
  base(int a) : a_(a) {}

  COMPARE
};

class foo : public base {
  int b_;

 public:
  foo(int a, int b) : base(a), b_(b) {}

  COMPARE
};

class bar : public base {
  int c_;

 public:
  bar(int a, int c) : base(a), c_(c) {}

  COMPARE
};

class baz : public bar {
  int d_;

 public:
  baz(int a, int c, int d) : bar(a, c), d_(d) {}

  COMPARE
};

      

Now, thanks COMPARE

, everyone has T::equal()

to be implemented and none of them can go back to an earlier implementation. Also, each class has its own operator==()

, which calls the appropriate one equal()

for its own type (and not the type of the base class).

I want to use these rules the way they do COMPARE

, but don't have to remember that every derived class must reference a macro and ideally (be C ++ idiomatic) without using a macro at all.

What's the correct C ++ way to do this?

+3


source to share


3 answers


I'm still involved as well, but what you're describing sounds like it might require a double dispatcher and / or visitor pattern.

For double sending, for example:

class base;
class foo;
class bar;
class baz;


class base {
    int a_;
public:
    base(int a) : a_(a) {}
    virtual bool operator==(const base&) const =0;
    virtual bool operator==(const foo&) const =0;
    virtual bool operator==(const bar&) const =0;
    virtual bool operator==(const baz&) const =0;
};

class foo : public base {
    int b_;
public:
    foo(int a,int b) : base(a),b_(b) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

class bar : public base {
    int c_;
public:
    bar(int a,int c) : base(a),c_(c) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

class baz : public bar {
    int d_;
public:
    baz(int a,int c,int d) : bar(a,c),d_(d) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

      



This is already very similar to the macro option presented above. :)

From the fourth edition of the TC ++ PL, section 22.3.1 on double dispatch, mentions possibly the use of a precomputed lookup table. Something like

bool equal(const base& b1,const base& b2)
    {
        auto i = index(type_id(b1),type_id(b2));
        return intersect_tbl[i](b1,b2);
    }

      

+3


source


except that the base class has an implementation whose derived classes should not by default (and this rule should apply)

If this is the only problem, then as Ben Weigt pointed out in his comment, this is not a problem, because pure virtual functions can be implemented.



Also, once the overridden class overrides, I need the same rule that the override is not used by other derived classes (if they must then they can always call this explicitly in their own implementation)

If so, then the "idiomatic C ++ way" probably won't use inheritance for the "derived" - "further derived" model. Inheritance is commonly used to model both substitutability and polymorphism, but it doesn't exist here. In other words: the model "obtained" - "further obtained" in composition.

0


source


So this appears to be one way to get derived classes to provide their own implementations of certain functions:

template<typename T> struct sweep : public T {
  template <class... Args> sweep(Args&&... args) : T(args...) { }
  virtual bool equal(base const &p) const = 0;
  virtual bool equal(foo const &p) const = 0;
  virtual bool equal(bar const &p) const = 0;
  virtual bool equal(baz const &p) const = 0;
  virtual bool operator==(base const &p) const = 0;
};

class base { ... };

class foo : public sweep<base> {
  int b_;

 public:
  foo(int a, int b) : sweep(a), b_(b) {}

  ...
};

      

It still requires the derived class to remember to do something specific in order to constrain itself - use a template sweep

to infer from the base class - but that's at least C ++, not C.

This also has the advantage that the template can update the default implementations rather than making them pure virtual; So:

template<typename T> struct sweep : public T {
  ...
  virtual bool equal(base const &p) const { return false; }
  ...
};

      

Based on the premise that without further guidance, every comparison must fail. This is actually closer to what I need - but not what I asked for.

0


source







All Articles