During the discussion came to this program:

#include <iostream> using namespace std; class A { protected: int var; public: A(int x) { var = x; // Это обращение к A::var } }; class B: public A { protected: int var; public: B():A(2) { var = 4; // Обращение к B::var } }; class C: public A { protected: int var; public: C():A(3) { var = 6; // Обращение к C::var } }; class D: public B, public C { protected: int var; public: void method() { var = B::A::var; // Должен выдать 2 cout << var << endl; var = C::A::var; // Должен выдать 3 cout << var << endl; var = B::var; // Должен выдать 4 cout << var << endl; var = C::var; // Должен выдать 6 cout << var << endl; } }; int main() { D obj; obj.method(); } 

The program compiles perfectly and displays what was expected in Visual C ++ 2015. Attempting to compile with the help of GCC on ideone.com gives a lot of errors:

 prog.cpp: In member function 'void D::method()': prog.cpp:46:21: error: 'A' is an ambiguous base of 'D' var = B::A::var; // Должен выдать 2 ^ prog.cpp:49:21: error: 'A' is an ambiguous base of 'D' var = C::A::var; // Должен выдать 3 ^ 

The question for experts on the standard is who is wrong here and who is right? If VC ++ is wrong, then what, why, and how to do it right ?

Update 10/22/2016

Perhaps the most portable is not to rely on the name lookup, but to use transformations in the spirit of

 var = static_cast<A*>(static_cast<B*>(this))->var; var = ((A*)(C*)this)->var; 

But at the same time, you need to make a var in public . (Again, I don’t understand why and where this reference is in the standard ...) The full code here is http://ideone.com/wt9yrz

  • In your example, there is no class D, therefore the error message is not relevant. :) - Vlad from Moscow
  • @VladfromMoscow Yes, sorry! Somehow I managed to lose it ... I didn’t give exactly those error messages. I hope now the question looks decent :) - Harry
  • clang, by the way, displays a chain of ambiguous inheritance paths in error. - αλεχολυτ
  • @alexolut But, it would seem, it is clearly indicated to him where to get the name from? - Harry
  • and can be immediately inherited through pure and not through public? - pavel

2 answers 2

As @Ant correctly pointed out, the studio is wrong here and the whole point is that such appeals by A::B::C::D::E::F are precisely appeals to nested entities, i.e. B must be an entity nested in A , С in B and so on. This can be seen in the non-normative reference in the standard:

[basic.lookup.qual] p2 [Note: Multiply qualified names, such as N1 :: N2 :: N3 :: n. can be used (9.7) or members of nested namespaces. - end note]

Those. we can refer to several levels, but these links should go downward, no ascending, or mixed levels.

Then why does it compile at all? (It compiles until it encounters ambiguity, remove ambiguity and it will work). Because there is the following rule for finding hidden names:

If you’re a class member, you can’t follow him?

And here it turns out that C::A::var should be interpreted as A::var , where C:: is an excess qualifier that does not carry any semantic load. There can be no other interpretation because C does not contain an internal class named A

The essence of the error, by the way, is clearly seen with Resharper: it immediately shows that the qualifiers C:: and B:: redundant.


Regarding the second part of the question (update): it does not work for a simple reason: the heir classes have access to the ancestor data only through this , they do not have access to the data of arbitrary objects of these classes. This is described in [class.access.base] p5 , and specifically, the case from the question, in (5.3) :

There is no need for any kind of legal action.

  • "C does not contain an internal class named A" . In fact this is not true. The class does not contain, but the type name contains. Each class contains within itself as a public member its own name - this is so called. injected-class-name - inherited by derived classes. Due to this, for example, we can in this example declare B::A a or class B::B::B::A::A::A::A a in main . - AnT
  • It seems that I understood the first part, but the second part is not very: how do we turn if not through this ? var = static_cast<A*>(static_cast<B*>(this))->var; ? - Harry
  • @AnT, well, this is not an expansion of visibility, i.e. it is not a nested type, right? - ixSci
  • @Harry, you led this to some type and it ceased to be this after that - it's just some kind of pointer. This case is considered in the part of the standard that I cited. How else could the compiler distinguish just A* from A* obtained from this ? - ixSci
  • @ixSci Then how correctly to address to this protected member of that A, which ancestor B? - Harry

GCC rights. Names of the form B::A::var and C::A::var are nothing more than qualified names, including names of class types B::A and C::A as nested-name-specifier . This is nothing more than an unambiguous way to refer to the base type itself, in which the name var will be searched. Both the name B::A and the name C::A refer to the same base type A He - ::A For this reason, both names are equivalent to each other and are also equivalent to ::A::var . That is, to expand the experiment, you can add to the function method() also access via ::A::var and get exactly the same error.

To emphasize this equivalence, you can rewrite the code inside method() so

 typedef B::A BA; typedef C::A CA; typedef ::A AA; static_assert(std::is_same<BA, AA>::value); static_assert(std::is_same<CA, AA>::value); BA::var; // неоднозначность CA::var; // неоднозначность AA::var; // неоднозначность 

Obviously, all three names — BA , CA and AA — are the same type. Therefore, there is no reason to expect that access through BA::var , CA::var or AA::var will behave differently.

Also note that if you eliminate multiple inheritance (and the ambiguity caused by it), access via ::A::var will work fine inside method() , despite the fact that if we consider it as a way of specifying an "access path", then it looks "wrong".

In other words, the nested-name-specifier in a qualified-id is not considered a language as an indication of a “pathway” through nested scopes in the name lookup process. The language in this case considers the nested-name-specifier only as a way of setting the scope in which to look for the name, after which the name is interpreted "on a general basis" in the context in which it is used.

PS It is interesting to note that in the original example, MSVC allows this

 void method() { var = ::A::var; cout << var << endl; } 

and prints 2 . That is, despite the fact that we did not take any measures to eliminate ambiguity, MSVC boldly believes that A::bar from B (obviously, the first in the list of base D ) should “win” in this case. There are no such rules in the language specification.

  • The way to refer to the base type turns out to be just ambiguous . It would be unambiguous with virtual inheritance. But how, then, to refer to the right instances of var from method ? - αλεχολυτ
  • @AnT Hmm, I somehow imagined it a little differently ... Well, it consoles at least what the VC ++ developers thought was exactly the same as me :) And about public A::var you cannot contribute clarity? - Harry
  • @alexolut: The way to refer to a type is unambiguous. In this program, only one type A The ambiguity here is only in the choice of a specific base sub-object by the name of type A - AnT
  • @alexolut Well, probably, as I suggested in the update to the question? But here comes the second question - why should A::var be public ? - Harry
  • @Harry: This is a protected access property. Class X access to protected members X allowed only through objects of type X or type inherited from X - AnT