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