mercredi 26 septembre 2018

How to allow custom return types of member functions for type erasure in C++?

Over the last years, type erasure or concept-based runtime polymorphism became quite popular, see e.g. the talks by Sean Parent Better Code: Runtime Polymorphism, Inheritance Is The Base Class of Evil or C++ Seasoning resp. the implementations by Adobe, Facebook, Boost.TypeErasure, Boost.te or dyno.

Here is an example from Sean Parent's talk:

class object_t {
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void draw_(ostream&, size_t) const = 0;
    };
    template <typename T>
    struct model final : concept_t {
        model(T x) : data_(move(x)) { }
        void draw_(ostream& out, size_t position) const override 
        { draw(data_, out, position); }
        T data_;
    };
    shared_ptr<const concept_t> self_;
public:
    template <typename T>
    object_t(T x) : self_(make_shared<model<T>>(move(x))){ }
    friend void draw(const object_t& x, ostream& out, size_t position)
    { x.self_->draw_(out, position); }    
};

In the example above any class T has to provide a function

void draw(T data, std::ostream out, size_t position);

which is simple to achieve for any T since the return type is void and the arguments signatures are all known a compile time. Same would be true, if the return type would by int, double, std::string etc.

Here is the actual question: How to do this for a custom return type RType resp. argument type ArgType?

class object_t {
    struct concept_t {
        virtual ~concept_t() = default;
        virtual void draw_(ostream&, size_t) const = 0;
        virtual RType foo (ArgType arg) const = 0; // new function
    };
    template <typename T>
    struct model final : concept_t {
        model(T x) : data_(move(x)) { }
        void draw_(ostream& out, size_t position) const override 
        { draw(data_, out, position); }
        RType foo (ArgType arg) const override 
        { return data_.foo(arg);}; // new function
        T data_;
    };
    shared_ptr<const concept_t> self_;
public:
    template <typename T>
    object_t(T x) : self_(make_shared<model<T>>(move(x))){ }
    friend void draw(const object_t& x, ostream& out, size_t position)
    { x.self_->draw_(out, position); }    
    RType foo (ArgType arg) const 
    { return self_->foo(arg);}; // new function
};

(i) If RType or ArgType would be an explicit type, i.e. there exists a type class MyClass{}; we could define using RType = MyClass; or using ArgType = MyClass and everything is fine.

(ii) If RType or ArgType would be a concept-based polymorphic type such as object_t it is also fine. But this would mean that the codebase would be full of interface classes such as object_t. Don't get me wrong this would be a fair price to pay, but it will take some time to get used to it and structure a codebase to do so.

Now the problematic part: Can I make the example work if I have something like this using RType = typename T::RType or using ArgType = typename T::ArgType for a class T???

I.e.

class T {
public:
    using ArgType = /*...*/;
    using RType = /*...*/;
/*...*/
}; 

Aucun commentaire:

Enregistrer un commentaire