14

Consider :

struct A { int x;};
struct B : A {};
struct C : private A {};

Now, as expected, the code

struct D : C
{
    D () { C::x = 2; }
};

int main () { D d; }

does not compile:

test2.cc: In constructor ‘D::D()’:
test2.cc:1:16: error: ‘int A::x’ is inaccessible
test2.cc:7:12: error: within this context

Now, if I do

struct D : B, C
{
    D () { C::x = 2; }
};

int main () { D d; }

then the error disappear! Isn't A::x supposed to be inaccessible too? What is the explanation here?

I'm using gcc version 4.7.2 (GCC), linux x86_64, if this matters.

EDIT: It does not compile with Clang 3.2: clang 3.2

But it does with gcc 4.7.2: gcc 4.7.2

6
  • Compiles there (same compiler though). Seems a GCC bug then? Commented Mar 13, 2013 at 23:37
  • 1
    Interesting: same compiler, different outcome. I can't think of anything else than a bug anyway Commented Mar 13, 2013 at 23:39
  • g++ 4.7.2 compiles the same source in both locations successfully. Commented Mar 13, 2013 at 23:39
  • Me dumb. Bad copy-paste. Sorry Commented Mar 13, 2013 at 23:40
  • there is the Clang output. Commented Mar 13, 2013 at 23:41

3 Answers 3

10

This is most certainly a bug. There is no reason why inheriting from class B as well should change the accessibility of C's members.

Not even GCC 4.8.0 (beta) seems to have solved this problem. Clang 3.2 and ICC 13.0.1, on the other hand, correctly refuse to compile this code.

Sign up to request clarification or add additional context in comments.

10 Comments

@ValeriAtamaniouk: I dunno, C::x pretty clearly scopes to C.
@GManNickG: Are you sure? That names A::x. It does not name the C subobject. If you want to name a member of the C subobject, use C* p = this; p->x
@BenVoigt: I don't get it: C::x should be resolved into C::A::x, or am I missing something? It seems GCC resolves it into B::A::x instead, which makes no sense
@BenVoigt I might be misunderstanding you, but I think C::x is supposed to refer specifically to the A::x in the C subobject and not be ambiguous, see this as an example
@BenVoight 10.1 p5 (in draft N3242, last public draft before C++11) "In such lattices, explicit qualification can be used to specify which subobject is meant. The body of function C::f could refer to the member next of each L subobject: void C::f() { A::next = B::next; } Without the A:: or B:: qualifiers, the definition of C::f above would be ill-formed because of ambiguity" ("such lattices" referring to multiple inheritance hierarchies where a base class is present more than once)
|
3

The answer is clang is correct. However, the code could also fail as ambiguous according to the standard.

If you look at 11.2p5 it has a relevant note (yes, I know notes are non-normative):

[ Note: This class can be explicit, e.g., when a qualified-id is used, or implicit, e.g., when a class member access operator (5.2.5) is used (including cases where an implicit “this->” is added). If both a class member access operator and a qualified-id are used to name the member (as in p->T::m), the class naming the member is the class denoted by the nested-name-specifier of the qualified-id (that is, T). —end note ]

What this note means, is that if you add this-> to C::x = 2; then C is the class naming the member and gcc 4.7.2 correctly fails when this is the case.

Now the question is Who is the class naming the member for C::x? The naming class is specified by the same 11.2p5:

The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found.

Now, name lookup for class members is specified in 10.2, and after reading all of it, I have concluded that x is the union of subobject sets as per:

Otherwise, the new S(f, C) is a lookup set with the shared set of declarations and the union of the subobject sets.

Which means that according to member lookup rules x can be either from B or A! This makes the code ill-formed as: Name lookup can result in an ambiguity, in which case the program is ill-formed. However, this ambiguity can be resolved as per 10.2p8:

Ambiguities can often be resolved by qualifying a name with its class name.

And from the Clang source, we can see that is what they chose to do:

// If the member was a qualified name and the qualified referred to a
// specific base subobject type, we'll cast to that intermediate type
// first and then to the object in which the member is declared. That allows
// one to resolve ambiguities in, e.g., a diamond-shaped hierarchy such as:
//
//   class Base { public: int x; };
//   class Derived1 : public Base { };
//   class Derived2 : public Base { };
//   class VeryDerived : public Derived1, public Derived2 { void f(); };
//   void VeryDerived::f() {
//     x = 17; // error: ambiguous base subobjects
//     Derived1::x = 17; // okay, pick the Base subobject of Derived1
//   }

However, note the can in the wording of the above quote: often can be resolved. This means that they do not necessarily have to be resolved. So, I think according to the standard the code should fail as ambiguous or as a private member access failure.

EDIT

There is some contention about the interpretation of can and as to whether a ambiguity occurs here. I found Defect report 39. Conflicting ambiguity rules talks about this issue.

6 Comments

I'm not sure I agree with your reading, did you see the example of in 10.1 p5? It seems clear that even without this-> there is not meant to be any ambiguity. (The case there is almost exactly the same as the ones cited in the question and your answer.)
@StephenLin: Note the wording can be used. I interpret that as meaning it is not mandatory.
I believe the word "can" here means that the situation "can be resolved" if the qualified-id is provided if the qualified-id resolves to a subobject for which there is only one member of that name (but might not be, if given the qualified-id there are still multiple subobjects with the same member name), not that the standard allows discretion on whether or not to resolve it. I would find it very unusual if the standard really gave implementations discretion on a basic compile-time semantic issue, rather than simply unspecified runtime behavior.
so the "can" is indicating logical possibility, not giving discretion...also, there is no note on the example of 10.1 p5 that says the behavior is optional, it says specifically that it is "well-formed" as a comment
@StephenLin: While I understand your interpretation, I believe they would use stronger wording if they wanted to make it mandatory. There is no reason why they use the wording can here except to give discretion.
|
-1

I am not sure of the exact cause but i know this:

when you use multiple inheritance in a diamond pattern like this one, you will have multiple copies of the base class A in you derived object D.

In your case the object D has 2 members named A::x which may cause confusion from the compiler.

this is known as the Diamon Problem

1 Comment

What does the diamond problem have to do with this question? The only scenario in which he creates a diamond-shaped inheritance graph is the second one, which he says works fine!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.