Search code examples
c++inheritancevirtual

Virtual Inheritance Issues


Consider the code below:

#include <iostream>
#include <string>

struct Thing {
    std::string name;
    int width, length, height, mass;
    Thing (const std::string& n) : name(n) {}
    Thing (const std::string& n, int w, int l, int h, int m) :
        name(n), width(w), length(l), height(h), mass(m) {}
    void print() const {std::cout << name << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
    virtual void foo() = 0;  // Abstract class
};

struct Clone : virtual Thing {
    Thing& parent;
    Clone (const std::string& name, Thing& p) : Thing(name), parent(p) {}
};

template <typename T>
struct ClonedType : public Clone, public T {
    ClonedType (const std::string& name, Thing& t) :
        Thing(name), Clone(name, t), T(name) {}
};

// virtual inheritance needed because of ClonedType<Blob> being instantiated:
struct Blob : virtual Thing {
    Blob (const std::string& name) : Thing(name, 3, 4, 5, 20) {}
    virtual void foo() override {}
};

int main() {
    Blob blob("Blob");
    ClonedType<Blob> b("New Blob", blob);
    blob.print();  // Blob, 3, 4, 5, 20
    b.print();  // New Blob, -1, -1, 4237013, 0  // Here's the problem!
}

Blob's constructor calls up Thing's constructor fine because of virtual inheritance, but because of that virtual inheritance ClonedType<T> fails to call up Thing's constructor through use of T's constructor. As a result, b, which is of type Blob, is not properly initialized (all Blob objects are to share the same values 3, 4, 5, 20, and other values like strings and special types that I'm not showing here). So how to fix this problem other than manually setting each of these values in the Blob constructor body (which would defeat the purpose of Thing(name, 3, 4, 5, 20))? Thing is an abstract class, by the way.

Update: I've added a solution below that works for the above question, but then in that solution I added more complications to this question that leaves the new problem unsolved.


Solution

  • Ok, I've found one working solution, with the added information that Thing is abstract (thus making the solution harder and the above solution invalid):

    #include <iostream>
    #include <string>
    
    struct Thing {
        struct PartialData { int width, length, height, mass; };  //**Added
        struct PartialDataTag {};  //**Added
        std::string name;
        int width, length, height, mass;
    
        Thing() = default;
        Thing (const std::string& n) : name(n) {}
        Thing (const std::string& n, int w, int l, int h, int m) :
            name(n), width(w), length(l), height(h), mass(m) {}
        Thing (const std::string& n, const PartialData& data) :  // ***Added
            name(n), width(data.width), length(data.length), height(data.height), mass(data.mass) {}
        void print() const {std::cout << name << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
    protected:
        void setUsingPartialData (const PartialData& data) {  // ***Added
            width = data.width;  length = data.length;  height = data.height;  mass = data.mass;
        }
        virtual void foo() = 0;  // Abstract class
    };
    
    struct Clone : virtual Thing {
        Thing& parent;
        Clone (const std::string& name, Thing& p) : Thing(name), parent(p) {}
    };
    
    template <typename T>
    struct ClonedType : public Clone, public T {
        ClonedType (const std::string& name, Thing& t) :
            Thing(name), Clone(name, t), T(PartialDataTag{}) {}  // ***Changed
    
    };
    
    struct Blob : virtual Thing {
        static const PartialData partialData;
        Blob (const std::string& name) : Thing (name, partialData) {}
        Blob (PartialDataTag) {setUsingPartialData(partialData);}  // ***Added
        virtual void foo() override {}
    };
    const Thing::PartialData Blob::partialData = {3, 4, 5, 20};  // ***Added
    
    int main() {
        Blob blob("Blob");
        ClonedType<Blob> b("New Blob", blob);
        blob.print();  // Blob, 3, 4, 5, 20
        b.print();  // New Blob, 3, 4, 5, 20
    }
    

    Can anyone think of a better solution? A lot of new added stuff to make it work, but at least the information 3, 4, 5, 20 is being used only once here.

    Right away, I can think of a serious drawback to this solution though. Suppose another concrete class derived from Thing uses a different type of partial data (say {int, int} only for width and mass), then my solution above won't work for this new class, right?

    Added to the question:

    struct Sample : virtual Thing {
        Sample (const std::string& name, int length, int height) :
            Thing(name, 10, length, height, 50) {}
        virtual void foo() override {}
    };
    

    How to instantiate

    ClonedType<Sample>
    

    and initialize it correctly? All Sample objects shall have width 10 and mass 50. Oh, and let's suppose furthermore that Thing has more std::string data members (also of static value for Sample) so that we cannot resort to using templates (e.g. Sample : Data<10, 50>) to define the Sample class.

    Update: Solved!

    #include <iostream>
    #include <string>
    
    struct Thing {
        struct PartialData { std::string codeName;  int width, length, height, mass; };
        struct PartialDataTag {};
        std::string name, codeName;
        int width, length, height, mass;
    
        Thing() = default;
        Thing (const std::string& n) : name(n) {}
        Thing (const std::string& n, int l, int h) : name(n), length(l), height(h) {}
        Thing (const std::string& n, const std::string& c, int w, int l, int h, int m) :
            name(n), codeName(c), width(w), length(l), height(h), mass(m) {}
        Thing (const std::string& n, const PartialData& data) :
            name(n), codeName(data.codeName), width(data.width), length(data.length), height(data.height), mass(data.mass) {}
        void print() const {std::cout << name << ", " << codeName << ", " << width << ", " << length << ", " << height << ", " << mass << '\n';}
    protected:
        void setUsingPartialData (const PartialData& data) {
            codeName = data.codeName;  width = data.width;  length = data.length;  height = data.height;  mass = data.mass;
        }
        virtual void foo() = 0;
    };
    
    struct Clone : virtual Thing {
        Thing& parent;
        template <typename... Args>
        Clone (Thing& p, Args&&... args) : Thing (std::forward<Args>(args)...), parent(p) {}
    };
    
    template <typename T>
    struct ClonedType : public Clone, public T {
        template <typename... Args>
        ClonedType (Thing& t, Args&&... args) : Thing (std::forward<Args>(args)...), Clone(t, std::forward<Args>(args)...), T(PartialDataTag{}) {}
    };
    
    struct Blob : virtual Thing {
        static const PartialData partialData;
        Blob (const std::string& name) : Thing (name, partialData) {}
        Blob (PartialDataTag) {setUsingPartialData(partialData);}
        virtual void foo() override {}
    };
    const Thing::PartialData Blob::partialData = {"Bob", 3, 4, 5, 20};  // !
    
    struct Sample : virtual Thing {
        static const int staticWidth = 10, staticMass = 50;
        Sample (const std::string& name, int length, int height) : Thing(name, "Sam", staticWidth, length, height, staticMass) {}
        Sample (PartialDataTag) {setUsingPartialData(getPartialData());}
        virtual void foo() override {}
        PartialData getPartialData() const {return {"Sam", staticWidth, length, height, staticMass};}  // !
    };
    
    int main() {
        Blob blob("Blob");
        ClonedType<Blob> b(blob, "New Blob");
        blob.print();  // Blob, Bob, 3, 4, 5, 20
        b.print();  // New Blob, Bob, 3, 4, 5, 20
    
        Sample sample("Sample", 11, 12);
        ClonedType<Sample> s(sample, "New Sample", 21, 22);
        sample.print();  // Sample, Sam, 10, 11, 12, 50
        s.print();  // Sample, Sam, 10, 21, 22, 50
    }