dimanche 21 avril 2019

How to forward constructors with CRTP and multiple inheritance C++

I was recently digging on using CRTP to clone objects with the use of covariance in c++11 and I found this : https://www.fluentcpp.com/2017/09/12/how-to-return-a-smart-pointer-and-use-covariance/ The post is clear and it works, in a way. What the post does not say is how to use parameters in constructors. I get the virtual trick with using Base::Base, though it forces the user to explicitely call the base constructors when instantiating the leaf class. This is only wanted if you're searching for something like diamond inheritance.

Though, in more usual cases, when you have several levels of inheritance, you have something like that: abstract A -> abstract B -> abstract C -> concrete D. I get why only the leaf class should be instantiable. But at each level, the constructors are more and more specific and with the example of CRTP design I linked, I don't get how to do it.

Hereinafter, what I came up with the simplification of having only one base class :

// Virtual and abstract tricks
template <typename Base>
class Virtual : virtual public Base
{
public:
    using Base::Base;
};
template <typename Object>
class Abstract
{
};

// General CRTP : Inheritance + implementation of clone function
template <typename Object, typename Base=void>
class Clone : public Base
{
public:
    virtual ~Clone() =default;
    std::unique_ptr<Object> clone() const {
        return std::unique_ptr<Object>(static_cast<Object *>(this->copy()));
    }
private:
    virtual Clone * copy() const override {
        return new Object(static_cast<const Object &>(*this));
    }
};

// Specialization : Inheritance + an pure cloning function
template <typename Object>
class Clone<Abstract<Object>>
{
public:
    virtual ~Clone() =default;
    std::unique_ptr<Object> clone() const {
        return std::unique_ptr<Object>(static_cast<Object *>(this->copy()));
    }
private:
    virtual Clone *copy() const =0;
};

And concerning my test classes:

class TestB0 : public Clone<Abstract<TestB0>>
{
public:
    TestB0() : B0(new int(0)) {cout<<"crea B0 vide"<<endl;}
    TestB0(int xB0) : B0(new int(xB0)) {cout<<"crea B0 "<<xB0<<" : "<<*B0<<endl;}
    TestB0(const TestB0 &t) : B0(new int(*t.B0)) {cout<<"copy B0 "<<*B0<<endl;}
    virtual ~TestB0() {delete B0;}
    void setB0(int xB0) {*B0=xB0;}
    int getB0() {return *B0;}
private:
    int *B0;
};

class TestB1 : public Clone<TestB1, TestB0>
{
public:
    TestB1() : B1(new int(0)) {cout<<"crea B1 vide"<<endl;}
    TestB1(int xB0=11, int xB1=20) : B1(new int(xB1)) {cout<<"crea B1 "<<xB0<<" "<<xB1<<" : "<<getB0()<<" "<<*B1<<endl;}
    TestB1(const TestB1 &t) : B1(new int(*t.B1)) {cout<<"copy B1 "<<getB0()<<" "<<*B1<<endl;}
    virtual ~TestB1() {delete B1;}
    void setB1(int xB1) {*B1=xB1;}
    int getB1() {return *B1;}
private:
    int *B1;
};

When I instanciate TestB1, I do not have access to the constructor of TestB0. I can use the virtual trick to do so :

class TestB1 : public Clone<TestB1, Virtual<TestB0>>

But when I do, I have to explicitely call the constructor in the leaf class. This is not a problem when I have only one level of inheritance. I think it is when I have more.

I'm unable to find a way to make the whole thing work in an elegant way. Is there a way to initialize the base class without using a virtual base ? In the way it's usually done when CRTP is not used.

Thank you for your anwsers.

As a comment : I'm creating polymorphic classes for a library that I want to be optimized from a time execution point of view (so without virtual tables). I can only work with c++11 so using declaration with pack expansion will not work as it does in c++17.

Aucun commentaire:

Enregistrer un commentaire