class A { public: int a; };
class B : public virtual A { public: using A::a; };
class C : public virtual A { public: using A::a; };
class D : public C, public B { };
class W { public: int w; };
class X : public virtual W { public: using W::w; };
class Y : public virtual W { };
class Z : public Y, public X { };
int main(){
D d;
d.a = 0; // Error
Z z;
z.w = 0; // Correct
return 0;
}
The first group of class declarations (A
, B
, C
and D
) and the second (W
, X
, Y
and Z
) are built similarly except that class C
has a using declaration (using A::a
) and class Y
doesn't.
When trying to access member a
in d.a = 0
I get an error in Clang (error: member 'a' found in multiple base classes of different types
) and in GCC (error: request for member 'a' is ambiguous
). I checked recent and old versions of both compilers and all of them agree. However, accessing w
in z.w = 0
compiles successfully.
What is the reason of this ambiguity when accessing a
?
To the best of my knowledge, both access declarations in classes B
and C
refer to the same base class member. And by the way, if I remove them the test compiles successfully because a
is already publicly accessible ( public
access specifier ).
Thanks in advance.
Note: The above code is a slightly modified test from SolidSands' SuperTest suite.
There is implementation variance here; ICC accepts your code while gcc, clang and MSVC reject it. ICC is correct and the other compilers are incorrect.
Running the [class.member.lookup] algorithm for D::a
, we find that:
a
in D
, so S(a, D) is initially empty and we merge in the lookup sets of a
in its base classes, calculated as follows:
Note that in the declaration set of S(a, B), the member is A::a
even though it is found in B
, and similarly for S(a, C):
In the declaration set, using-declarations are replaced by the set of designated members [...]
To determine whether the member access d.a
is ambiguous, we now check [expr.ref]/5:
5 - [The] program is ill-formed if the class of which
E2
is directly a member is an ambiguous base of the naming class ofE2
[...]
Here E2
has been determined to be A::a
, a direct member of A
. The naming class is D
. A
is not an ambiguous base of D
, since A
is a virtual base of all the intermediate base class subobjects of D
. So d.a
is unambiguous both in name lookup and in member access, and your program is correct.
As an analogous instance, we can consider replacing virtual inheritance with a static member per the note to [class.member.lookup]/9:
9 - [ Note: A static member, a nested type or an enumerator defined in a base class
T
can unambiguously be found even if an object has more than one base class subobject of typeT
. Two base class subobjects share the non-static member subobjects of their common virtual base classes. — end note ]
struct A { static int a; };
struct B : A { using A::a; };
struct C : A { using A::a; };
struct D : B, C { };
int main() {
D d;
d.a = 0; // OK
}
Here again we have S(a, D) = { { A::a }, { B, C } }. Indeed, name lookup proceeds the same way even if A::a
were a non-static member of a non-virtual base; the name lookup is unambiguous but the member access [expr.ref] is ambiguous in that case.
To further elucidate the distinction between name lookup and member access, consider that even for a non-static data member of a non-virtual multiply inherited base class, it is possible to unambiguously use the name of the member taking the derived class as the naming class:
struct A { int a; };
struct B : A { using A::a; };
struct C : A { using A::a; };
struct D : B, C { };
int B::*p = &D::i; // OK, unambiguous
Unfortunately, every compiler I have tried rejects this despite this being (modulo using-declarations) an example in the Standard!