Search code examples
c++polymorphismc++17

Polymorphic value types and interfaces


I have a polymorphic value type implemented like so:

class ShapeValue {
  public:
    template<class T>
    ShapeValue(const T& value) {
       obj = make_unique<holder<T>>(value);
    }
    // ... appropriate copy constructors and such

    void draw() { obj->draw(); }

  private:

    struct base {
       virtual ~base() {}
       virtual void draw() = 0;
    };

    template<class T>
    struct holder<T> : public base {
       T value;
       void draw() override { value.draw(); }
    }

    unique_ptr<base> obj;
};

If you aren't familiar with this sort of thing, here's a good talk.

Ok, that's great. But now what if I want to cast my underlying object to some other interface?

Here's my motivation. Previously, I had defined things the typical way, like so:

class Shape {
   virtual void draw() = 0;
};

and then I would define other interfaces, like:

class HasColor {
   virtual Color color() = 0;
   virtual void setColor(Color) = 0;
};

so I could define a shape as follows:

class MyShape : public Shape, public HasColor {
   void draw() override;
   Color color() override;
   void setColor(Color) override;
};

So if I have a bunch of selected shapes and I want to set their color, I could iterate over all shapes and dynamic_cast<HasColor*>. This proves to be quite convenient (my actual app isn't a drawing app, by the way, but has analogous data).

Can I do this for my polymorphic value type, in a way that my ShapeValue interface doesn't need to know about every Has interface? I could do the following, which isn't actually so bad, but not ideal:

HasColor* ShapeValue::toHasColor() { return obj->toHasColor(); }

Solution

  • A solution (tested) is to have a base class for the interfaces:

    class AnyInterface {
       virtual ~AnyInterface() {} // make it polymorphic
    };
    
    struct HasColor : public AnyInterface {
       // ... same stuff
    };
    

    So then we have the following:

    vector<AnyInterface*> ShapeValue::getInterfaces() { return _obj->getInterfaces(); }
    
    

    Could then define a helper to grab the interface we want:

    template<class I>
    I* hasInterface(Shape& shape) {
       for(auto interface : shape.getInterfaces()) {
           if(auto p = dynamic_cast<I*>(interface)) {
               return p;
           }
       }
       return nullptr;
    }
    

    This way ShapeValue does not need to know about all the interface types.