Embed an implementation in one multifunctional interface class - many CRTP classes?

How to create many classes to act as an implementer for an interface class, while avoiding the expense of a v-table and still allowing static casting to the interface?

For a simple case, this can be achieved, as in the example below.

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 expand the function described above to support 2 routes or more? enter image description here

The code below is incompatible, but it depicts my dream. ( full demonstration ).

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 : -

, , "", .

  • RouterI1U2<User> RouterI2U1<User>
  • RouterI1U1<User> RouterI2U2<User>
  • { RouterI1U1<User> RouterI1U2<User>} i2()
  • { RouterI2U2<User> RouterI2U1<User>} i1()
  • i1() i2()

.

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

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

(), . ( )
  RouterI1U2 RouterI2U1 RouterI1U2_I2U1.

+4
2

, .

. - . I i1 i2 -:

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

, , :

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

i1 i2 "", Model<T>, , .

:

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

, . I. -, I .

I . User2 - , I:

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?

"". , - - .

Model<T> , i1 i2. , , u1 u2.

, , , T - i1 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 {};

, u1 u2, i1 i2 :

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

.

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

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

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

 struct User4 {
    void u1() {}
    void u2() {}
};
+2

.

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

... .

+1

Source: https://habr.com/ru/post/1684180/


All Articles