Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

inject implementation to a single multi-function interface class - many CRTP classes?

Tags:

How to create many classes to act like implementer for an interface class, while avoid v-table cost as possible, and still enable static casting to the interface?

For a simple case, it can be achieved like in the below example.

Example

Library Code :-

class I{ //interface
    public: virtual void i1()=0;
};
template<class Derived>class Router : public I{ 
    public: virtual void  i1()final{   
        //in real case it is very complex, but in the core is calling :-
        static_cast<Derived*>(this)->u1(); 
    }
};

User Code :-

class User : public Router<User>{
    public: void u1(){ std::cout<<"hi"<<std::endl; }
};
int main() {
    User u;
    u.i1();   //<-- no v-table cost
    I* i=&u;
    i->i1();  //<-- has v-table cost (OK)
}

enter image description here

Full demo

Question

How to extend the above feature to support 2 routes or more? enter image description here

The below code is uncompilable, but it depicts my dream. (full demo).

Library Code :-

class I{ //interface
    public: virtual void i1()=0;
    public: virtual void i2()=0;
};
template<class Derived>class RouterI1U1 : public I{ 
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u1(); }
};
template<class Derived>class RouterI1U2 : public I{ 
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u2(); }
};
template<class Derived>class RouterI2U1 : public I{ 
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u1(); }
};
template<class Derived>class RouterI2U2 : public I{ 
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u2(); }
};

User Code :-

The one who want to use the above library, can easily pick whatever "route" he want.

  • derived from RouterI1U2<User> and RouterI2U1<User> or
  • derived from RouterI1U1<User> and RouterI2U2<User> or
  • derived from {RouterI1U1<User> or RouterI1U2<User>} and implement i2() with final manually or
  • derived from {RouterI2U2<User> or RouterI2U1<User>} and implement i1() with final manually or
  • implement i1() and i2() with final manually

Here is an dreamed example of usage.

class User : public RouterI1U2<User>,public RouterI2U1<User>{ 
    public: void u1(){ std::cout<<"hi1"<<std::endl; }
    public: void u2(){ std::cout<<"hi2"<<std::endl; }
};
int main() {
    User u;
    u.i1();   //<-- no v-table cost
    I* i=&u;
    i->i1();  //<-- has v-table cost (OK)
}

My poor solution

class I{ //interface
    public: virtual void i1()=0;
    public: virtual void i2()=0;
};

template<class Derived> class RouterI1U2_I2U1 : public I{ //group it
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u2(); }
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u1(); }
};
class User : public RouterI1U2_I2U1<User>{ 
    public: void u1(){ std::cout<<"hi1"<<std::endl; }
    public: void u2(){ std::cout<<"hi2"<<std::endl; }
};

It works (demo), but offer less modularity. (low re-usability)
I have to pack RouterI1U2 and RouterI2U1 to RouterI1U2_I2U1 manually.

like image 891
cppBeginner Avatar asked Aug 22 '17 03:08

cppBeginner


2 Answers

It may not apply in your case, but could be useful to other reading the question as well.

I suggest you to use the concept-model idiom for this particular case. The goal of this is to separate the polymorphism implementation and the implementation of those class themselves into different part. Here, I becomes a polymorphic wrapper around any class that has i1 and i2 member functions:

class I {
    // The interface is internal, invisible to outside
    // We use this as a type erasure technique and polymorphism
    struct Concept {
        virtual void i1() = 0;
        virtual void i2() = 0;
    };

    // The single implementation that directly
    // extend the interface is the model. T is the user class.
    // T must have i1 and i2 function, because we call them.
    template<typename T>
    struct Model : Concept {

        // The user class.
        // If you want, you can use a reference there if you
        // need references semantics with I
        T user;

        Model (T u) : user{std::move(u)} {}

        // The only implementation of i1 is to call i1 from the user class
        void i1() override {
            user.i1();
        }

        void i2() override {
            user.i2();
        }
    };

    // Or use a shared, or use SBO
    std::unique_ptr<Concept> concept;

public:
    // When we make an I, we must provide a user class.
    // If the user class had i1 and i2, it will compile.
    // If Model takes a reference, use a reference there too.
    template<typename T>
    I(T model) : concept{std::make_unique<Model<T>>(std::move(model))} {}

    void i1() {
        concept->i1();
    }

    void i2() {
        concept->i2();
    }
};

Then, your classes that provide implementations becomes like this:

template<class Derived>
struct RouterI1U1 { // no Inheritance needed
    void i1() { static_cast<Derived*>(this)->u1(); }
};

template<class Derived>
struct RouterI1U2 { 
    void i1() { static_cast<Derived*>(this)->u2(); }
};

template<class Derived>
struct RouterI2U1 { 
    void i2() { static_cast<Derived*>(this)->u1(); }
};

template<class Derived>
struct RouterI2U2 { 
    void i2() { static_cast<Derived*>(this)->u2(); }
};

Since these i1 and i2 only needs to be "there" in order to fit into the Model<T> class, no overriding is needed, so no virtual inheritance.

It would effectively look like this when used:

struct User : RouterI2U2<User> {
    void i1() {}
    void u2() {}
};

As you can see, we don't have any virtual method. The polymorphism is an implementation detail of I. Since this class have all required member functions, the I class will allow it.

And using the class I too is quite simple. Let User2 be another user class that fits I requirements:

User2 user2;

user2.i1(); // no vtable, so no vtable overhead possible

I myI{user2}; // works!

myI.i2(); // calls u2, with vtable

std::vector<I> v;

v.emplace_back(User2{});
v.emplace_back(User{}); // simple heh?

Here's how you can remove the router classes, and implement this thing with an "or" style interface. I mean an interface that allows you to implement something or something else.

In the Model<T> class, you can check if i1 and i2 exists. If there are not existing, you can provide an implementation that calls u1 and u2 instead.

We start that by making type traits that can tell us if a particular type T has the member function i1 or i2:

template<typename...>
using void_t = void;

template<typename, typename>
struct has_i1 : std::false_type {};

template<typename T>
struct has_i1<T, void_t<decltype(std::declval<T>().i1())>> : std::true_type {};

template<typename, typename>
struct has_i2 : std::false_type {};

template<typename T>
struct has_i2<T, void_t<decltype(std::declval<T>().i2())>> : std::true_type {};

Now, we can change our model implementation to call u1 or u2 if i1 and i2 are not there:

template<typename T>
struct Model : Concept {
    T user;

    Model(T u) : user{std::move(u)} {}


    void i1() override {
        i1_helper(user);
    }

    void i2() override {
        i2_helper(user);
    }

private:
    template<typename U>
    auto i1_helper(U& u) -> std::enable_if_t<has_i1<U>::value> {
        // Call i1 if has i1
        u.i1();
    }

    template<typename U>
    auto i1_helper(U& u) -> std::enable_if_t<!has_i1<U>::value> {
        // Call u1 if has not i1
        u.u1();
    }

    template<typename U>
    auto i2_helper(U& u) -> std::enable_if_t<has_i2<U>::value> {
        // Call i2 if has i2
        u.i2();
    }

    template<typename U>
    auto i2_helper(U& u) -> std::enable_if_t<!has_i2<U>::value> {
        // Call u2 if has not i2
        u.u2();
    }
};

Now, your user classes are the simplest possible.

struct User1 {
    void i1() {}
    void i2() {}
};

struct User2 {
    void i1() {}
    void u2() {}
};

struct User3 {
    void u1() {}
    void i2() {}
};

 struct User4 {
    void u1() {}
    void u2() {}
};
like image 52
Guillaume Racicot Avatar answered Sep 29 '22 21:09

Guillaume Racicot


Use virtual inheritance.

template<class Derived>class RouterI1U1 : public virtual I{ 

etc. makes the code compilable.

like image 40
n. 1.8e9-where's-my-share m. Avatar answered Sep 29 '22 22:09

n. 1.8e9-where's-my-share m.