dimanche 3 février 2019

Using friend functions to retrieve type information from a polymorphic class?

I have a question about using friend functions inside a nested polymorphic class to retrieve type information.

I have the following code which demonstrates what I'm doing. Given two classes, A<T> and B<T>, I can create a runtime polymorphic wrapper that holds an A or B. In practice, this wrapper could hold anything, including another templated class that has a similar static interface.

template<typename T>
struct A {
  T value_;

  A(T value) : value_(value) {}

  void sayHello() const {
    std::cout << "Hello from A! " << value_ << '\n';
  }
};

template<typename T>
struct B {
  T value_;

  B(T value) : value_(value) {}

  void sayHello() const {
    std::cout << "Hello from B! " << value_ << '\n';
  }
};

The wrapper comes from Sean Parent's Runtime Polymorphism concepts, but I have a need to retrieve the type information for some operations. For example, maybe I can add an A and a B, but not an A and a C. Basically, if I place a friend function within the templated wrapper class, I can cast the object back to its original type.

class Wrapper {
private:
  class Concept {
  public:
    virtual ~Concept() = default;

    virtual void sayHello() const = 0;
  };

  template<typename T>
  class Model final
      : public Concept {
  private:
    T data_;

  public:
    Model(T data) : data_(data) {}

    virtual void sayHello() const override {
      data_.sayHello();
    }

  private:
    template<typename U>
    friend inline void doSomething(const Concept &lhs, const B<U> &rhs) {
      T x = static_cast<const Model<T> &>(lhs).data_;

      x.sayHello();
      rhs.sayHello();

      auto y = x.value_ + rhs.value_;

      std::cout << y << '\n';
    }
  };

  template<typename U>
  friend inline void doSomething(const Concept &lhs, const B<U> &rhs);

  std::shared_ptr<const Concept> ptr_;

public:
  template<typename T>
  explicit inline Wrapper(T a)
      : ptr_(std::make_shared<Model<A<T>>>(std::move(a))) {}

  template<typename U>
  friend inline void someFriend(Wrapper &lhs, B<U> &rhs) {
    doSomething(*lhs.ptr_, rhs);
  }
};

Note that I'm able to static_cast the Concept class within the friend function because its type can be deduced from within the context of the Model<T> class.

So I can use the code like this:

Wrapper a(1);
B<int> b{2};

someFriend(a, b);

Which outputs:

Hello from A! 1
Hello from B! 2
3

My question is whether there is some kind of unforeseen problem doing things this way. Also, if I replace the object held in the pointer, should the static_cast still work?

Some preliminary tests I've run show that it's fairly reliable, but I occasionally run into the problem that the call seems to 'specialize' around the first object and then doesn't switch if the pointer changes to a new object.

Here is a link to the code.

Aucun commentaire:

Enregistrer un commentaire