Taylor
Taylor

Reputation: 6410

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(); }

Upvotes: 3

Views: 882

Answers (2)

Louis Langholtz
Louis Langholtz

Reputation: 3123

The accepted answer seems likely a viable solution though I haven't tested it and it does seem to fallback to reference semantics. A motivating factor however for polymorphic value types is instead value semantics.

What follows is a description of a more value semantic oriented alternative solution where ShapeValue doesn't need to know about all the interface types, albeit external user-definable free functions sort of do instead.

As I've been using polymorphic value types, I've preferred to recognize two categories of functionality of those values:

  1. Functionality required of all eligible value types. I.e. the functionality enforced by the virtual methods of this base polymorphic concept class.
  2. Optional/extended functionality which some, none, or all eligible value types may provide.

It seems like your question is more about how to deal this second category (than the first).

For this second category, I've borrowed on the implementation of std::any's type member function and std::any's non-member any_cast template functions. With these two functional concepts, the set of value types, which implement some optional extended functionality, is open (like namespaces are open to additions contrary to classes) and your ShapeValue's interface doesn't need to know about every optional extension. As an added bonus, no extended functionality needs to be implemented using type polymorphism - i.e. the value types eligible for use with ShapeValue construction, don't have to have any kind of inheritance relationship or virtual functions.

Here's an example of pseudo code extending the question's code for this:

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

    ShapeValue& operator=(const ShapeValue& newValue) {
        obj = newValue.obj? newValue.obj->clone(): nullptr;
        return *this;
    }

    const std::type_info& type() const noexcept {
        return obj? obj->type_(): typeid(void);
    }

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

    template <typename T>
    friend auto type_cast(const ShapeValue* value) noexcept {
        if (!value || value->type() != typeid(std::remove_pointer_t<T>))
            return static_cast<T>(nullptr);
        return static_cast<T>(value->obj->data_());
    }

  private:

    struct base {
       virtual ~base() = default;
       virtual void draw() = 0;
       virtual std::unique_ptr<base> clone_() const = 0;
       virtual const std::type_info& type_() const noexcept = 0;
       virtual const void* data_() const noexcept = 0;
    };

    template<class T>
    struct holder final: base {
       T value;
       void draw() override { value.draw(); }
       std::unique_ptr<base> clone_() const override {
           return std::make_unique<holder>(value);
       }
       const std::type_info& type_() const noexcept override { return typeid(T); }
       const void* data_() const noexcept override { return &value; }
    };

    unique_ptr<base> obj;
};

template <typename T>
inline auto type_cast(const ShapeValue& value)
{
    auto tmp = type_cast<std::add_pointer_t<std::add_const_t<T>>>(&value);
    if (tmp == nullptr)
        throw std::bad_cast();
    return *tmp;
}

struct Square {
    int side_;
    Color color_;
    void draw();
    Color color() { return color_; }
    void setColor(Color value) { color_ = value; }
};

Color color(const ShapeValue& value)
{
    if (value.type() == typeid(Square)) {
        return type_cast<Square>(value).color();
    }
    throw std::invalid_argument("color not supported for value's type");
}

void setColor(ShapeValue& value, Color newColor)
{
    if (value.type() == typeid(Square)) {
        auto square = type_cast<Square>(value);
        square.setColor(newColor);
        value = square;
        return;
    }
    throw std::invalid_argument("setColor not supported for value's type");
}

For a more elaborate, compilable, tested, and typeid/std::type_info-free example, one can take a look at the source code for the Joint polymorphic value type I just finished that provides an interface to value types for constraining the movements of one or more bodies. I wouldn't say it's perfect, but it's also more value semantics oriented like the example above that I've included in this answer.

Upvotes: 3

Taylor
Taylor

Reputation: 6410

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.

Upvotes: 2

Related Questions