This code example would describe the language feature I find non-intuitive.
class A {
public:
A() {}
};
class B: private A
{
public:
B() {}
};
class C: public B
{
public:
C() {}
void ProcessA(A* a) {
}
};
int main() {
C c;
}
Compilation of this code with Apple LLVM version 4.2 on Mac produces an
test.cc:16: error: ‘class A’ is inaccessible
test.cc:16: error: within this context
Replacing void ProcessA(A* a) with void ProcessA(::A* a) would make it build but I don't understand why should I use absolute class name here.
Is it a language feature that is there to avoid certain kind of errors or is it just a dark C++ grammar corner like requirement to put space between angle brackets (> >) in templates parametrized with other templates.
Thanks!
I 'll start by describing what happens here -- forgive me if you already know this, but it creates necessary context for the follow-up.
The compiler resolves the unqualified A to ::C::A (the result will be the same if you make the change at source level yourself). Since ::C::A is inaccessible an error message is emitted.
You are proposing that the compiler should detect that ::C::A is inaccessible and the reference to A should then be considered a reference to ::A as a fallback. However, ::C::A and ::A may easily be two entirely different things.
Automatically guessing what should be done here is not only prone to introducing bugs and/or hair-pulling¹, but also completely contrary to the spirit of C++.
Confirmation that this behavior is conformant and by-design, directly from the C++11 standard.
§9/2 says:
A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name.
This means that inside the scope of class C, A is an injected-class-name.
§3.4/3 states that the injected-class-name is a candidate for name lookups:
The injected-class-name of a class is also considered to be a member of that class for the purposes of name hiding and lookup.
§3.4/1 clarifies that the inaccessibility of the base A does not prevent the injected-class-name A from being considered:
The access rules are considered only once name lookup and function overload resolution (if applicable) have succeeded.
§11.1/5 gives a direct explanation of the exact situation under discussion:
[Note: In a derived class, the lookup of a base class name will find the injected-class-name instead of the name of the base class in the scope in which it was declared. The injected-class-name might be less accessible than the name of the base class in the scope in which it was declared. —end note ]
The standard also gives this example, which is equivalent to yours:
class A { };
class B : private A { };
class C : public B {
A *p; // error: injected-class-name A is inaccessible
::A *q; // OK
};
¹ Imagine what happens if A is initially a public base, then later becomes private during a refactoring. Also imagine that ::A and ::C::A are unrelated. You would expect that a call like a->foo() (which used to work) would fail because foo is no longer accessible, but instead of this the type of a has changed behind your back and you now get a "there is no method foo" error. Huh?!? And that's of course far from the worst that could happen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With