3

I am studying C++ and while studying virtual inheritance, I came across following doubt:

class A {
public:
    int x;
    A() { x = 677; }
    A(int a) { 
        cout << "A con , x= " << a << endl;
        x = a;
    }
};
class B : public A {
public:
    B(int a) : A(a) { }
};
class C :public A {
public:
    C(int a) : A(a) { }
};
class D : public B, public C {
public:
    D(int a,int b) : B(a),C(b) { }
};
int main()
{
    D d(5,6);
    cout << d.A::x;  //prints 5 
 }

In line cout << d.A::x; It prints 5. Shouldn't this call be ambiguous and if not why it prints 5?

Vivek Mangal
  • 532
  • 1
  • 8
  • 24
  • 1
    How many different compilers did you try? Check if GCC, Clang and MSVC all agree or not. If not, you're probably flirting with undefined behaviour. – Louis Cloete Nov 15 '21 at 13:44
  • Did you enable compiler warnings? – sweenish Nov 15 '21 at 13:48
  • 1
    Latest GCC and Clang gives compile errors on Godbolt default settings. MSVC is happy to compile, but I don't read MASM well enough to be able to tell you what it would output and Godbolt doesn't have the option to execute code compiled with MSVC at the moment. – Louis Cloete Nov 15 '21 at 13:50
  • 1
    @LouisCloete different compilers not agreeing is a rather weak indicator for UB. Very often different compilers don't disagree even though there is UB. And sometimes they disagree without any UB in sight – 463035818_is_not_an_ai Nov 15 '21 at 13:56

2 Answers2

2

d.A::x; is indeed ambiguous. GCC and Clang report it as error, only MSCV fails to do so: https://godbolt.org/z/1zhjdE6a8.

There is a note in [class.mi] with an example of multiple inheritance stating that:

In such lattices, explicit qualification can be used to specify which subobject is meant. The body of function C​::​f can refer to the member next of each L subobject:

void C::f() { A::next = B::next; }      // well-formed

Without the A​::​ or B​::​ qualifiers, the definition of C​::​f above would be ill-formed because of ambiguity ([class.member.lookup]). — end note]

It is just a note, because it follows from [class.member.lookup] (which is a little more contrived to read and understand) that without the qualifier the member access is ambiguous. "ill-formed" implies that a conforming compiler must issue either an error or warning.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

This is ambiguous according to both GCC and Clang, but MSVC compiles it (Godbolt)

You can solve it by using virtual inheritance for B and C if it makes sense to only have one instance of A in D or you can specify via which parent the call must access A::x if you need two distinct instances of A in D.

std::cout << d.B::x; // go through B to its A
std::cout << d.C::x; // go through C to its A

The above compiles on all 3 compilers without error, because it is unambiguous. It specifies through which intermediate class to access A::x.

Louis Cloete
  • 421
  • 3
  • 16
  • 3
    Be careful about recommending virtual inheritance. It is not a **coding** solution to ambiguities; it's a **design** decision. Yes, it could remove the ambiguity, but if there are supposed to be two `x` members, changing to virtual inheritance breaks the design. – Pete Becker Nov 15 '21 at 14:21
  • 1
    @PeteBecker I added a part about the tradeoffs with virtual inheritance and gave an alternative if you need two unique instances. Do you agree with it? – Louis Cloete Nov 15 '21 at 14:34