Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the way to reach a field or a method of a far away base class in a double diamond?

In the "classical" diamond problem like in the following (one where there is no virtual in front of public D, behind class C and class B), one may resolve the ambiguity using the namescope operator ::, such as in the constructor of class A:

/* 
 *   D                            D   D
 *  / \   which without 'virtual' |   |
 * B   C      is actually:        B   C
 *  \ /                            \ /
 *   A                              A
 */
#include <iostream>
using namespace std;
class D                      { public: char d = 'D';};
class C : public D           { public: char c = 'C';};
class B : public D           { public: char b = 'B';};
class A : public B, public C { public: char a = 'A'; A();};

A::A() {
    cout << B::d; //This works! B's d, inherited from D.
    cout << C::d; //This works! C's d, inherited from D.
    //cout << D::d;     //This doesn't work (ambiguous)
    //cout << B::D::d;  //Doesn't work either though.
    //cout << C::D::d;  //Doesn't work either though.
}

int main() {
    A a;
    cout << endl;
    return 0;
}

Consider a double diamond such as this now:

/* 
 *   G                          G  G G  G
 *  / \                         |  | |  |
 * E   F                        E  F E  F
 *  \ /                          \ / \ /
 *   D                            D   D
 *  / \   which without 'virtual' |   |
 * B   C      is actually:        B   C
 *  \ /                            \ /
 *   A                              A
 */
#include <iostream>
using namespace std;
class G                      { public: char g = 'G';};
class E : public G           { public: char e = 'E';};
class F : public G           { public: char f = 'F';};
class D : public E, public F { public: char d = 'D';};
class C : public D           { public: char c = 'C';};
class B : public D           { public: char b = 'B';};
class A : public B, public C { public: char a = 'A'; A();};

A::A() {
    cout << /* How do I reach any of the two e's or f's 
               or any of the four g's?*/
}

int main() {
    A a;
    cout << endl;
    return 0;
}

How exactly would one reach the fields inherited from E, F and G? What actually seemed most logical to me was the following.

cout << B::D::d;
cout << B::D::E::e; 
cout << B::D::F::f; 
cout << B::D::E::G::g; 
cout << B::D::F::G::g; 

cout << C::D::d;
cout << C::D::E::e; 
cout << C::D::F::f; 
cout << C::D::E::G::g; 
cout << C::D::F::G::g; 

However (using g++) they all yield an error of the form 'X' is an ambiguous base of 'A'..

Can somebody explain why this doesn't work and what's the proper way to do this? What am I missing?

like image 222
ihato Avatar asked Dec 04 '19 04:12

ihato


2 Answers

The reason this doesn't work:

cout << B::D::d;

is because: the scope resolution is right-left associative; this is in a sense like (B::D) :: d, although parentheses aren't actually allowed here. So the qualified lookup B::D is resolved, and that finds the type D. There's only one type called D , there are not separate types A::D and B::D. The same type can be found in multiple scopes.

Therefore you get the equivalent of D::d which is ambiguous since there are multiple paths to a base of type D .


To get at the variable you want, you may have to use a series of casts, e.g.:

cout << static_cast<G&>(static_cast<E&>(static_cast<D&>(static_cast<B&>(*this)))).g;

In C-style syntax you can use ((G&)(E&)(D&)(B&)(*this)).g although that is dangerous as if you make a mistake in the class ordering you'll get a reinterpret_cast instead of a static_cast which can malfunction.

Actually in this case you can omit the D step, since a B has a unique E base:

cout << static_cast<G&>(static_cast<E&>(static_cast<B&>(*this))).g;

or even:

cout << static_cast<B&>(*this).E::g;

since there is a unique E::g once we are at B.


It is also possible to use dynamic_cast instead of static_cast, I'm open to comments about which one would be a better style :)

like image 167
M.M Avatar answered Oct 19 '22 13:10

M.M


Let's start with the error you're getting. 'X' is an ambiguous base of 'A'.

The compiler is telling you that there's an ambiguity/uncertainty in getting the parent of the object you've selected for std::cout. Without specifying abstract base classes, you're correct in the inheritance structure.

/* 
 *   G                          G  G G  G
 *  / \                         |  | |  |
 * E   F                        E  F E  F
 *  \ /                          \ / \ /
 *   D                            D   D
 *  / \   which without 'virtual' |   |
 * B   C      is actually:        B   C
 *  \ /                            \ /
 *   A                              A
 */

Now the problem is actually in what you're writing to standard out. here's what you've written:

cout << B::D::d;
cout << B::D::E::e; 
cout << B::D::F::e;  // When does F inherit from E and get member variable e?
cout << B::D::E::G::g; 
cout << B::D::F::G::g; 

cout << C::D::d;
cout << C::D::E::e; 
cout << C::D::F::e; // When does F inherit from E???
cout << C::D::E::G::g; 
cout << C::D::F::G::g; 

I have yet to test this with g++ (I'm using MSVC++), but this works:

A::A() {
    std::cout << B::b
              << B::D::d
              << B::D::E::e
              << B::D::F::f
              << B::D::E::G::g << std::endl;
    /* How do I reach any of the two e's or f's
               or any of the four g's?*/
}

Cheers!

Edit: Here's the error you're facing in MSVC++ Image describing compile time error of F trying to access member variable E... "ambiguous"

Here's some proof of it working in MSVC++": enter image description here

Now in g++:

#include <iostream>
#include <stdio.h>
using namespace std;
class G                      { public: char g = 'G';};
class E : virtual public G           { public: char e = 'E';};
class F : virtual public G           { public: char f = 'F';};
class D : public E, public F { public: char d = 'D';};
class C : virtual public D           { public: char c = 'C';};
class B : virtual public D           { public: char b = 'B';};
class A : public B, public C { public: char a = 'A'; A();};

A::A() {
    std::cout << A::a
              << A::B::b
              << A::B::D::d
              << A::B::D::E::e
              << A::B::D::F::f
              << A::B::D::E::G::g 
              << std::endl;
}

int main() {
    A a;
    cout << endl;
    return 0;
}

This should work.

like image 1
jsonV Avatar answered Oct 19 '22 13:10

jsonV