1
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.

José Luis
  • 397
  • 1
  • 11
  • 2
    `d.a` has the choice between `d.B::a` and `d.C::a` (even if in reality, there are the same). – Jarod42 Jun 19 '17 at 09:12
  • why not to use `d.A::a = 0;`? – W.F. Jun 19 '17 at 09:13
  • 1
    I couldn't find at [cppreference](http://en.cppreference.com/w/cpp/) any documentation of `using` that applies here (only alias, and namespace-related uses). – Walter Jun 19 '17 at 09:18
  • @Jarod42 Thanks for the comment. I understand your explanation and I thought the same. However, if I remove both access declarations (`using A::a`) in classes `B` and `C` the problem should be the same because member `a` is still accesible through both `B` and `C` paths. Nevertheless, in this case the program compiles successfully. Do you know why one compiles and the other doesn't? – José Luis Jun 19 '17 at 14:17
  • @W.F. Because the goal of the program is to determine if a compiler can resolve this ambiguity or not. Thanks in any case for the alternate proposal. – José Luis Jun 19 '17 at 14:24
  • As You use virtual inheritance, there is only one `A`, so not without `using`, you only have `d.A::a`. Don't know why `z.w` is correct BTW, as you have normally `z.W::w` and `z.X::w`... – Jarod42 Jun 19 '17 at 14:48
  • @Walter http://en.cppreference.com/w/cpp/language/using_declaration – Cubbi Jun 19 '17 at 17:11
  • 1
    The member access rules changed in C++11. And as far as I can see, your example is perfectly valid for C++11. C++03 rules *I believe* renders your first code ill-formed because it doesn't consider using declarations transparent (lookup doesn't look through them for C++03): It will find "w" in B and C, and neither is more dominant than the other. For the second testcase it finds "w" in W and X, but X is more dominant than W, so X wins. In C++11, in both testcases it finds "w" in W and A respectively and there's then no ambiguity. – Johannes Schaub - litb Jun 24 '17 at 10:32
  • @JohannesSchaub-litb Thanks a lot. Regarding C++03, it would be ill-formed `If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects`. Having that `Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration.` Shouldn't it mean that even if `a` is found in both `B` and `C` it's considered to be from `A`? So it's the same `a` and there's no ambiguity. – José Luis Jun 26 '17 at 15:38
  • 1
    @jos yes, so I don't know, seems to be valid in C++03 as well then. On the other hand, it doesn't say that the two using declarations are merged. It only says that both are considered to be from the same subobject (for the purpose of those two checks). So the name lookup still finds two declarations. Perhaps that is why the compiler complains? – Johannes Schaub - litb Jun 26 '17 at 16:17

1 Answers1

1

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:

  • There is no declaration of 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:
    • S(a, B) = { { A::a }, { B } }
    • S(a, C) = { { A::a }, { C } }
  • The resulting lookup set is S(a, D) = { { A::a }, { B, C } }

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 of E2 [...]

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 type T. 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!

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • The last part of your answer has been a long standing issue with existing implementations that claim to implement C++11, see https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/_GKE1I7YkkY Also https://www.thecodingforums.com/threads/class-member-name-lookup-issues.742289/#post-4178715 – Johannes Schaub - litb Jun 24 '17 at 10:36
  • Also https://groups.google.com/forum/#!msg/comp.lang.c++.moderated/SuZAsTsL0zQ/PpjnP0IMfakJ for an unclarity in the wording surrounding overload resolution with the rules of 10.2 . All of this hadn't been resolved (to the best of my knowledge) neither in the spec nor in implementations even though I posted DRs directly to the people in charge. – Johannes Schaub - litb Jun 24 '17 at 10:50
  • @ecatmur Thanks a lot for your explanation, I agree the code is valid for C++11 and C++14. Do you think the code is valid for C++03 as well? The exact algorithm you reference was not defined yet and there was instead only one paragraph (10.2/2) in [class.member.lookup] to define this behaviour (here there is a quote of the C++03 paragraph: https://stackoverflow.com/a/7211847/6892577) – José Luis Jun 26 '17 at 15:09
  • 1
    @JoséLuis I believe it is valid for C++03 as well. Name hiding doesn't arise, so all that matters is that `d.B::a` is "considered to be" from `d.B::A`, and similarly `d.C::a` from `d.C::A`, which are the same sub-object because of virtual inheritance; so `d.a` unambiguously designates the `a` non-static data member of the unique base class subobject `A`. – ecatmur Jun 27 '17 at 10:23
  • @ecatmur Great, I think now we all agree it is valid. Thank you very much for your help. – José Luis Jun 27 '17 at 10:43