I am using private inheritance, and I am surprised to see that in the derived class use of any base object is not allowed.
class A;
class B : private A;
class C : public B;
C::method_1()
{
A* a; // Temporary "A" object for local computation
}
This has nothing to do with inheritance. I don't want to access any this->base method!
This configuration provide a C2247 error in Visual Studio (" 'A' not accessible, because 'B' use 'private' to inherit from 'A' ").
Change this:
A* a;
to this:
::A* a;
since C inherits from B, and B from A, thus you need the scope resolution operator to do the trick.
Instead of starting at the local scope which includes the class parents, ::A starts looking at the global scope because of the ::.
From the Standard:
11.1.5 Acess Specifiers
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.
ISO C++: 11.1 Access Specifiers
5 [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]
And the example from the standard:
class A { };
class B : private A { };
class C : public B {
A* p; // error: injected-class-name A is inaccessible
::A* q; // OK
};
N3797 Working Draft, Standard for Programming Language C++
Related
Consider a class D inherited from two classes B and C, each of which inherits not-virtually class A. There is a method f in A, and the same named method in B hiding the method from A. I would like to call A::f() from B-base class of D object as follows:
struct A { void f() {} };
struct B : A { void f() {} };
struct C : A {};
struct D : B, C {};
int main() { D{}.B::A::f(); }
Unfortunately, it works only in MSVC, while both GCC and Clang produce the error:
error: 'A' is an ambiguous base of 'D'
demo: https://gcc.godbolt.org/z/jY3v876hK
It looks like GCC/Clang accept but completely ignore B:: prefix in B::A::f(). Are they right in doing so according to the standard?
Are they right in doing so according to the standard?
Yes. The nested name specifier in A::, B::A, or D::B::A all serve the same purpose, to name the class A.
[basic.lookup.classref]
4 If the id-expression in a class member access is a qualified-id of the form
class-name-or-namespace-name::...
the class-name-or-namespace-name following the . or -> operator is first looked up in the class of the object expression ([class.member.lookup]) and the name, if found, is used. Otherwise it is looked up in the context of the entire postfix-expression.
The nested name specifier does not name "a path" to the member, it names a base of D. And to name A results in ambiguity.
Other answers show why the compiler is correct.
In order to achieve what you want, you can do:
int main() { static_cast<B&&>(D{}).A::f(); }
It's a bit ugly, though.
https://gcc.godbolt.org/z/Mzs8rsdf5
Here's some contrived example code:
template<typename T> void Do(T arg) { (void)arg->b; }
namespace A {
struct Foo { int a; };
}
namespace B {
struct Foo { int b; };
struct Bar : A::Foo {
void Blah() { Do((Foo *)0); }
};
}
Which when compiled with gcc 4.8.2 (clang gives a similar error):
namespacebug.cpp: In instantiation of ‘void Do(T) [with T = A::Foo*]’:
namespacebug.cpp:10:34: required from here
namespacebug.cpp:1:39: error: ‘struct A::Foo’ has no member named ‘b’
template<typename T> void Do(T arg) { (void)arg->b; }
^
Note in the error it refers to T = A::Foo even though at the call-site I am creating a Foo within namespace B. If I remove the base class decl (: A::Foo) then all compiles fine.
This appears to suggest that inheriting from A::Foo somehow brings it into my namespace and matches it to my use of Foo? What C++ "feature" causes this?
(Of course, this issue can easily be fixed by namespacing my use of Foo, but that's not the question.)
Because of the injected-class-name rule, the name of a class is visible as though it were a member.
9/2
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. For purposes of access checking, the injected-class-name is treated as if it were a public member name.
So it is as though class A::Foo contains a member Foo which names the type A::Foo. Since name lookup in Bar::Blah() considers base members of Bar before namespace members, the name lookup for Foo finds the injected-class-name, which names A::Foo.
Does inheriting from a class bring it into the namespace?
Sort of. If lookup of a name fails in the class, then lookup is continued in the base class.
10.2 Member name lookup
...
5 Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).
Member name lookup includes lookup of nested types too. Later in that same section, we find:
9 [ Note: A static member, a nested type or an enumerator defined in a base class T can unambiguously be found even if an object has more than one base class subobject of type T. Two base class subobjects share the non-static member subobjects of their common virtual base classes. —end note ]
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!
Human talk
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++.
Standardese
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.
class A {};
class B : private A {
};
class C : public B {
public:
void f() {
A a; // This line causes error, but works when it is in main() function
}
};
int main()
{
C c;
// A a; --> This line works
return 0;
}
I am guessing this has something to do with B inheriting privately from A but cannot put my finger on it.
EDIT: Error is "class A is not visible". Compiled with g++.
name lookup is separate from access checking. and when you inherit from a class, that class' name is injected into the inheriting class' scope. so in class C you pick up the name A but it's not accessible.
one solution is to write ::A a; instead of A a;.
While Alf's answer is correct, I feel that it could be made a bit clearer (or maybe more confusing, who knows). The name of a class is injected into the scope of the class itself. As he mentions access specifiers are only checked after lookup finds what the identifier means. In the code in questions:
class A {};
class B : private A {};
struct C : public B {
void f() {
A a; // This line causes error, but works when it is in main() function
}
};
When processing C::f the compiler sees the identifier A and tries to resolve it. It searches in the scope of C and it is not present, it moves up the hierarchy and does not find it in B, but it finds it inside the base type A (lookup resolves the unqualified A to be ::A::A). In this context the identifier A is resolved to the injected name A inside the base class A of my base class B and access specifiers are checked. The compiler checks whether it can access my base class B, and then whether it can access it's base class A but this one is inaccessible due to the private inheritance and complains that you cannot access the nested name A inside A.
By providing the extra qualification ::A you are directing lookup. In this case, it will find the type A in the global namespace, which is perfectly accessible and it will compile.
Is the following code valid with C++ standard? It's strange that C::B would work because struct B is in A's namespace. But it does compile fine with gcc.
struct A { struct B {}; };
struct C : public A::B {};
struct D : public C::B {};
If this is standard conforming C++, what are reasonable applications for this construct?
Thanks.
Yes, it's valid C++. A class in its own scope (so both B and B::B refer to the same class B), and a class's parent class is in its own scope. So since B is in C's scope and B is in its own scope, C::B refers to B which is A::B.
(Side note: do not confuse a namespace with a scope.)
C++03 §9 paragraph 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.
For purposes of access checking, the injected-class-name is treated as if it were a public member name.
This is standard conforming C++.
A reasonable application of nested classes (through scope resolution) is the pImpl design pattern (though you can implement it in a way that will not demonstrate nested classes, but here I choose to demonstrate nested classes).
Note: Inheritance is one of the facets that can be confusing but the real concept is the scope resolution of a nested class.
//ExposedClass in Exposed.h
class CExposedClass
{
public:
CExposedClass();
~CExposedClass();
void doThis();
void doThat();
private:
class CXImpl;
CXImpl *pImpl;
};
//ExposedClass Impl in Exposed.cpp
#include "Exposed.h"
class CExposedClass::CXImpl
{
int someData;
};
CExposedClass::CExposedClass():pImpl(new CXImpl()){}
CExposedClass::~CExposedClass(){delete pImpl;}
void CExposedClass::doThis(){}
void CExposedClass::doThat(){}
class defined within the scope of one class is addressed from another scope using scope resolution.