15

MSVC, Clang and GCC disagree on this code:

struct Base { int x; };
struct Der1 : public  Base {};
struct Der2 : public  Base {};

struct AllDer : public Der1, public Der2 {
    void foo() {
        Der1::Base::x = 5;
    }
};

Godbolt

GCC:

<source>: In member function 'void AllDer::foo()':    
<source>:10:21: error: 'Base' is an ambiguous base of 'AllDer'    
   10 |         Der1::Base::x = 5;    
      |                     ^    
Compiler returned: 1

Clang gives a similar error, and MSVC gives no error.

Who is right here?

I suppose this is covered in [class.member.lookup], but I have difficulties understanding what it is trying to tell me for this case. Please quote the relevant parts and if possible explain in plain English.

PS: Inspired by this question Why is Reference to Base Class ambiguous with :: -operator trough derived class?

PPS: Actually my doubt is whether Der1::Base refers to the type, that would be Base (and then Der2::Base is exactly the same type), or to the subobject. I am convinced that it is the first, but if it is the latter then MSVC would be right.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    https://stackoverflow.com/questions/25952681/when-using-multiple-inheritance-why-is-this-qualified-name-ambiguous https://stackoverflow.com/questions/46648905/scope-operator-for-base-class-in-super-multiple-non-virtual-inheritance – Language Lawyer Apr 28 '20 at 10:23
  • @LanguageLawyer more good hints that gcc and clang are right, but I am not fully convinced that mscv is wrong – 463035818_is_not_an_ai Apr 28 '20 at 10:25
  • 1
    Obviously both refer to the same type `::Base`, but the real question seems to be slightly different here. There are two subobjects of type `Base`, and both have a `Base::x` member. – MSalters Apr 28 '20 at 10:46
  • @MSalters the PPS merely expresses my confusion. If you have a suggestion for a better title feel free to edit, I don't like it myself (because I know the answer is yes), but didn't find something more appropriate – 463035818_is_not_an_ai Apr 28 '20 at 10:48
  • https://www.geeksforgeeks.org/virtual-base-class-in-c/ – kesarling He-Him Apr 28 '20 at 11:21
  • 1
    @d4rk4ng31 there is no virtual inheritance in the code (on purpose) – 463035818_is_not_an_ai Apr 28 '20 at 11:34
  • @d4rk4ng31 There are valid uses for non virtual, multiple identical base types, as seen in the above example. Usually virtual inheritance is used when you derive from an "interface" class (and then, not for all interface classes). – curiousguy Apr 29 '20 at 17:14

2 Answers2

6

To answer the question in the title, yes, Derived1::Base references the injected-class-name [class.pre] Base and so does Derived2::Base. Both refer to the class ::Base.

Now, if Base would have a static member x, then the lookup of Base::x would be unambiguous. There's only one.

The problem in this example is that x is a non-static member, and AllDer has two such members. You can disambiguate such access to x by specifying an unambiguous base class of AllDer which has only one x member. Derived1 is an unambiguous base class, and it has one x member, so Derived1::x unambiguously specifies which of the two x members in AllDer you mean. Base too has only one x member, but it is not an unambiguous base of AllDer. Every instance of AllDer has two sub-objects of type Base. Therefore Base::x is ambiguous in your example. And since Derived1::Base is just another name for Base, this remains ambiguous.

[class.member.lookup] specifies that x is looked up in the context of the nested-name-specifier, so that has to be resolved first. We are indeed looking for Base::x, not Derived1::x, because we started by resolving Derived1::Base as Base. This part succeeds, there's only one x in Base. Note 12 in [class.member.lookup] explicitly tells you that an using an unambiguous name lookup may still fail when there are multiple subobjects with that same name. D::i in that example is basically your Base::x.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • so it only "may fail" but not "must fail" and all three compilers are right? Consider for example a `template struct foo : A,B` with some contrived code to access members of a base of `A` and `B`. In such case I want to get an error – 463035818_is_not_an_ai Apr 28 '20 at 11:39
  • 1
    @idclev463035818: I suspect it's a "must fail" _diagnostic required_. In particular, this fails on 7.6.1.4/7 Class member access, "ill-formed". – MSalters Apr 28 '20 at 11:50
  • I have to do a bit more reading. And I have to do some research on msvc, I suspect that some flags are needed to get fully compliant output, but have to find out which flags – 463035818_is_not_an_ai Apr 28 '20 at 11:53
2

The reason you can refer to the class name as a member of the class is because cpp aliases it for convenient use, as if you wrote using Base = ::Base; inside Base.
The problem you’re facing is that Der1::Base is Base.
Thus, when you write Der1::Base::x, it’s the same as Base::x.

Daniel
  • 30,896
  • 18
  • 85
  • 139
  • I know what is "the problem" but you are not answering my question: "Who is right? (and why?)" – 463035818_is_not_an_ai Apr 28 '20 at 09:53
  • @idclev463035818: I believe this is the right side. The part about `using` is written in the standard, and I think that’s the key to interpreting what the expression says. – Daniel Apr 28 '20 at 10:04
  • @idclev463035818: Look at the following example. I think it will clear it up a bit. https://ideone.com/IqpXjT – Daniel Apr 28 '20 at 10:14
  • sorry but I think you dont get the point of my question. I want to know what is right according to the standard, because I see different compiler do different things. Looking at what one particular compiler does to one particular example, does not help to answer the question – 463035818_is_not_an_ai Apr 28 '20 at 10:18
  • Just FYI, MSVC (ver 19.25.28614 for x64) fails to compile your example at [ideone.com/IqpXjT](https://ideone.com/IqpXjT). Using `cl /c /Wall /WX /Od /MDd /Za /permissive- /std:c++17 main.cpp` as its command line, I get `main.cpp(7): error C2597: illegal reference to non-static member 'A::x'` – heap underrun Apr 28 '20 at 16:38