Multiple instances of a virtual base class subobject (really) -- no way?

265 views Asked by At

Given the code:

#include <cassert>


struct X {};

struct Y1: virtual X {};
struct Y2: virtual X {};
struct Y3: virtual X {};
struct Y4: virtual X {};

struct Z1: Y1, Y2 {};
struct Z2: Y3, Y4 {};

struct XYZ: Z1, Z2 {};


int main() {
    XYZ xyz;

    X *x1 = static_cast<Y1*>(&xyz);
    X *x2 = static_cast<Y2*>(&xyz);
    X *x3 = static_cast<Y3*>(&xyz);
    X *x4 = static_cast<Y4*>(&xyz); 

    assert( x1 == x2 ); //first pair, ok
    assert( x2 == x3 ); //can we make this one fail?
    assert( x3 == x4 ); //second pair, ok


    return 0;
}

can we make the second assert fail?

In other words, this is the case when we have a two-diamond inheritance graph and would like to have separate subobjects for both diamonds' tops in the most derived object.

The standard (2003, 10.1.4) wording seems to prohibit this, and if really so, the follow-up question is: are we given no means of precise virtual-over-multiply-included subobject structure manipulation, and why?

4

There are 4 answers

1
Mark B On

Once a base is declared virtual, all sources of that virtual base are collapsed into one base of that type, it won't let you split it off halfway up the hierarchy (there's nothing you can say at a child to de-virtual a parent). Just to be clear, inheriting from a different class that non-virtually inherited the base would result in another instance. You could use composition in XYZ to create the two instances instead of inheritance, and then use a normal interface to delegate as appropriate.

0
celtschk On

As a basic rule, all virtual base classes of the same type are merged (non-virtual base classes are not merged with the virtual one, though). There's no mechanism to block a virtual base class from being shared. The reason probably is that any such mechanism would have needed quite some effort to design (and also effort for compiler writers to implement) for very little gain (did you ever get into the situation where you actually wished for that functionality?)

0
Jon G. On

You can achieve some sort of comparison via double dispatch. It's not perfect. This can be done with less code too, but I just wanted to show the idea behind it.

class BaseX {
    bool Equals(BaseX* potentialBaseX) {
        if(potentialBaseX) {
            return potentialBaseX->TestEquals(this);
        }
        // handles null
        return false;
    }

    // OK: x to x
    virtual bool TestEquals(BaseX* baseX) { return true; }
    virtual bool TestEquals(DerivedY1* derivedY) { return false; }
    virtual bool TestEquals(DerivedY2* derivedY) { return false; }
    virtual bool TestEquals(DerivedY3* derivedY) { return false; }
    virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};
class DerivedY1 {
    // OK: y1 to y1, y1 to y2
    virtual bool TestEquals(BaseX* baseX) { return false; }
    virtual bool TestEquals(DerivedY1* derivedY) { return true; }
    virtual bool TestEquals(DerivedY2* derivedY) { return true; }
    virtual bool TestEquals(DerivedY3* derivedY) { return false; }
    virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};    
class DerivedY2 {
    // OK: y2 to y2, y2 to y1
    virtual bool TestEquals(BaseX* baseX) { return false; }
    virtual bool TestEquals(DerivedY1* derivedY) { return true; }
    virtual bool TestEquals(DerivedY2* derivedY) { return true; }
    virtual bool TestEquals(DerivedY3* derivedY) { return false; }
    virtual bool TestEquals(DerivedY4* derivedY) { return false; }
};
class DerivedY3 {
    // OK: y3 to y3, y3 to y4
    virtual bool TestEquals(BaseX* baseX) { return false; }
    virtual bool TestEquals(DerivedY1* derivedY) { return false; }
    virtual bool TestEquals(DerivedY2* derivedY) { return false; }
    virtual bool TestEquals(DerivedY3* derivedY) { return true; }
    virtual bool TestEquals(DerivedY4* derivedY) { return true; }
};
class DerivedY4 {
    // OK: y4 to y4, y4 to y3
    virtual bool TestEquals(BaseX* baseX) { return false; }
    virtual bool TestEquals(DerivedY1* derivedY) { return false; }
    virtual bool TestEquals(DerivedY2* derivedY) { return false; }
    virtual bool TestEquals(DerivedY3* derivedY) { return true; }
    virtual bool TestEquals(DerivedY4* derivedY) { return true; }
};

//Using your example:
assert( x1.Equals(x2) ); //first pair, ok
assert( x2.Equals(x3) ); //can we make this one fail?
assert( x3.Equals(x4) ); //second pair, ok
0
curiousguy On

Closest thing (not pretty):

struct XYZ1;
struct XYZ2;

struct XYZ1 : Z1 {
    XYZ2 &self;
    XYZ1 (XYZ2 &self) : self(self) {}
};
struct XYZ2 : Z2 {
    XYZ1 &self;
    XYZ2 (XYZ1 &self) : self(self) {}
};
struct XYZ {
    XYZ1 m1;
    XYZ2 m2;
    XYZ() : m1(m2), m2(m1) {}
};