I have a base class defining a constant and the child class can use it using alias. The construct is as below
class Base
{
protected:
static const int A_ = 1;
};
class Foo : public Base
{
private:
using Base::A_;
};
However, when I define a subclass of Foo as
class Go : public Foo
{
private:
using Base::A_;
};
the compiler emits the error: error: ‘const int Base::A_’ is private within this context. I do not get it since Base::A_ is protected. What did the compiler see in this case and what can be the solution to use Base::A_ in Go ?
I do not get it since Base::A_ is protected.
Go inherits from Foo not from Base. A_ is private in Foo not protected.
If you want to have access to A_ in classes derived from Foo then A_ should be public or protected in Foo.
tl;dr
From a strict standards perspective none of the 3 compilers are right.
Your example should be ill-formed in C++20 (and previous versions), because it violates 9.9 [namespace.udecl] (17):
(17) [...] The base class members mentioned by a using-declarator shall be visible in the scope of at least one of the direct base classes of the class where the using-declarator is specified.
The commitee also confirmed this rationale in CWG 1960:
Rationale (November, 2014):
The rule was introduced because the hiding of a base class member by an intermediate derived class is potentially intentional and should not be capable of circumvention by a using-declaration in a derived class. The consensus of CWG preferred not to change the restriction.
... but in practice none of the 3 major compilers (gcc, clang, msvc) implements this correctly.
This will most likely be fixed in C++23; The cited paragraph from above will be removed by P1787, thereby making your example well-formed.
The paper also mentions the sad reality of current implementations: P1787 R6
CWG1960 (currently closed as NAD) is resolved by removing the rule in question (which is widely ignored by implementations and gives subtle interactions between using-declarations).
gcc has a name-resolution bug that causes your compiler error: gcc Bug 19377
1. Disclaimer
This post refers only to the C++20 standard; i haven't checked all relevant sections in all previous standards.
In my examples i'll use both static and non-static data members; the rules regarding using-declarations are the same for both. 1
This does not extend to other class member declarations; notably nested class and enumeration type members (class.qual 1.4) and constructors (class.qual 2.2, namespace.udecl) have a few special rules.
2. Long Explanation
The main rule we need to consider to check if your code is well formed is:
9.9 The using declaration [namespace.udecl] (17)
(17) In a using-declarator [...] all members of the set of introduced declarations shall be accessible. [...] In particular, if a derived class uses a using-declarator to access a member of a base class, the member name shall be accessible. [...] The base class members mentioned by a using-declarator shall be visible in the scope of at least one of the direct base classes of the class where the using-declarator is specified.
So there are two checks that a member using-declarator must pass for it to be well-formed:
The member it refers to must be accessible
The member it refers to must be visible within a direct base class
2.1 Pre-reqs
There are a few key concepts we need to cover before we can get to performing those two checks:
2.1.1 Visibility and Accessibility
I'm going to assume familiarity with visibility and accessibility.
Here's a quick definition if required:
Accessiblity refers to private, protected, public access control
Visibility refers to name hiding, e.g. in the following example the outer x variable is hidden by the inner x variable (so within the inner scope the outer x variable is NOT visible):
{
int x;
{
int x;
}
}
Here's a godbolt that demonstrates all 4 possible combinations of accessibility and visibility for class members.
2.1.2 using-declarators hide members of base classes
Another key concept we need to cover is that using-declarators introduce synonyms - those synonyms are member-declarations and as such can hide members of the same name from base classes.
This is given by 9.9 The using declaration [namespace.udecl]
(1) Each using-declarator in a using-declaration introduces a set of declarations into the declarative region in which the using-declaration appears. [...] [The unqualified-id of the] using-declarator is declared in the declarative region in which the using-declaration appears as a synonym for each declaration introduced by the using-declarator. [...]
(2) Every using-declaration is a declaration and a member-declaration and can therefore be used in a class definition.
and 6.4.10 Name hiding [basic.scope.hiding]:
(1) A declaration of a name in a nested declarative region hides a declaration of the same name in an enclosing declarative region.
This basically means that using Base::A_ within Foo will create a synonym - Foo::A_ - that refers to Base::A_.
This synonym hides the original Base::A_ member.
Note that the accessibility of Base::A_ has not been changed - it is still public - Base::A_ is just not visible anymore because the synonym Foo::A_ hides it within Foo and Go.
Here's a small example similar to your example: godbolt
struct Base {
static const int A_ = 1;
};
struct Foo : Base {
private:
// this introduces a synonym for Base::A_ which hides Base::A_
// (only the synonym is visible within Foo and Go)
using Base::A_;
};
struct Go : Foo {
int go();
};
In this case the following applies:
Within the scope of Go:
Base::A_ is NOT visible, but it is accessible (hidden by Foo::A_)
Foo::A_ (the synonym) is visible, but it is NOT accessible
Note that we can't access A_ within Go directly because it would resolve to Foo::A_ which is not accessible:
int Go::go() {
// resolves to the synonym Foo::A_, which is private in Foo
// -> ill-formed
return A_;
}
But we can still access Base::_A from within Go (it is accessible, just not visible) by using a nested-name-specifier: 2
int Go::go() {
// resolves directly to Base::A_, which is public in Base
// -> well-formed
return Base::A_;
}
This is possible because nested-name-specifiers only consider the specified class scope, i.e. in this case it'll only look for A_ in Base, but NOT in Foo. There's also a mention of this in class.qual.
2.2 So is your example well-formed?
So lets get back to actually checking if your code example is well-formed:
The member it refers to must be accessible
The member it refers to must be visible within a direct base class
2.2.1 Is Base::A_ accessible?
using-declarations are resolved using qualified name lookup, so the name must be resolved within the given class.
9.9 The using declaration[namespace.udecl]
1 [...] The set of declarations introduced by the using-declarator is found by performing qualified name lookup for the name in the using-declarator. [...]
In your case this means that using Base::A_; within Go must only consider members within Base for this check - NOT within Foo.
Base::A_ is a public member
Go inherits publically from Foo, which in turn inherits publically from Base
So Base::A_ must be accessible within Go.
Note that this is where gcc messes up - instead of only considering members of Base it also checks in Foo and finds the synonym, which produces the bogus compiler error about Foo::A_ not being accessible.
This is most likely related to gcc Bug 19377.
gcc also doesn't like (some) nested name specifiers; note that if we use the example from above but use a non-static data member then gcc suddenly doesn't like the nested name specifier:
godbolt
struct Base {
int A_ = 1;
};
struct Foo : Base {
private:
using Base::A_;
};
struct Go : Foo {
int go() {
// resolves to the synonym Foo::A_ (which is private)
// -> ill-formed
// all 3 compilers agree
return A_;
// nested-name-specifier that resolves directly to Base::A_
// -> well-formed
// (only gcc wrongly rejects this)
return Base::A_;
// same as above, just with explicit this
// -> well-formed
// all 3 compilers agree (even gcc, hooray!)
return (*this).Base::A_;
return this->Base::A_;
}
};
2.2.2 Is Base::A_ visible within a direct base class?
Note that is calls for a check of visibility within a direct base class - in your case Go only inherits from Foo, so Foo is the only direct base class of Go.
As covered above Base::A_ is NOT visible within Foo (it gets hidden by the synonym Foo::A_), so this check should fail (and therefore your example should be ill-formed)
However neither gcc, clang nor msvc actually do this check (even though it is clearly mandated by the standard).
So none of those 3 compilers are conforming to the standard in this case.
This has also been covered in a standard defect report: CWG 1960
The base class members mentioned by a using-declaration shall be visible in the scope of at least one of the direct base classes of the class where the using-declaration is specified.
The rationale for this restriction is not clear and should be reconsidered.
Rationale (November, 2014):
The rule was introduced because the hiding of a base class member by an intermediate derived class is potentially intentional and should not be capable of circumvention by a using-declaration in a derived class. The consensus of CWG preferred not to change the restriction.
which has been resolved in 2014 as NAD (not a defect), i.e. clearly indicating that the standard committee wants your example to be ill-formed.
In case you're interested those are the open compiler bugs in gcc and clang for this: (unresolved for quite some time now)
gcc: Bug 32039 - Using declaration accepts non-visible members from base classes
clang: Issue 20624 - No diagnostic where using-declaration names entity not found by class member name lookup in any direct base
3. C++23 to the rescue!
The paper P1787R6 will remove the visibility check completely from that paragraph:
(17) In a using-declarator that does not name a constructor, all members of the set of introduced [every] declaration[s] [named] shall be accessible. > In a using-declarator that names a constructor, no access check is performed.
In particular, if a derived class uses a using-declarator to access a member of a base class, the member name shall be accessible. If the name is that of an overloaded member function, then all functions named shall be accessible. The base class members mentioned by a using-declarator shall be visible in the scope of at least one of the direct base classes of the class where the using-declarator is specified.
P1787R6 will most likely be merged into the C++ Standard with C++23.
So in C++23 your example should be well-formed out of the box.
There's even a rationale for the change in that paper:
CWG1960 (currently closed as NAD) is resolved by removing the rule in question (which is widely ignored by implementations and gives subtle interactions between using-declarations).
4. Potential fix for C++ Versions before C++23
A potential way in which you could make your code well-formed before C++23 would be to make Base a direct base class of Go.
A cheesy way you could accomplish this would be by using virtual inheritance:
godbolt
struct Base {
static const int A_ = 1;
int B_ = 1;
};
struct Foo : virtual Base {
private:
using Base::A_;
using Base::B_;
};
struct Go : Foo, virtual Base {
using Base::A_;
using Base::B_;
};
Related
Consider the following snippet:
struct Base{
void f() {}
};
struct Derived: public Base{
void f() { Base::f(); }
};
Derived d;
int main(){
d.f();
}
The snippet works fine. i.e., d.f() calls Base::f(). But I can't find a quote from the standard supporting this.
All standard references below refer to N4659: March 2017 post-Kona working draft/C++17 DIS.
This is [class.qual]/1 [extract, emphasis mine]:
If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class [...]
In your example, Base:: is the nested-name-specifier, and the name specified after it is f(), for which lookup is thus performed in the scope of the class Base. This rule will not be affected by shadowing on the call site, as we are using a qualified name, and we may note that if we remove f() from Base the qualified id will (as expected for qualified lookup) not search for f() of the scope of the derived class.
struct Base {};
struct Derived: public Base {
void f() {
Base::f(); // error: 'f' is not a member of 'Base
}
};
We may also note that [class.qual]/3 particularly mentions this case:
A class member name hidden by a name in a nested declarative region or by the name of a derived class member can still be found if qualified by the name of its class followed by the :: operator.
but as pointed out in a comment by #LanguageLawyer, this paragraph should arguably be non-normative(1).
(1) [class.qual]/3 gives a specific example of how [class.qual]/1 can apply / the effect of it. However, these kind of segments in the standard are typically non-normative (examples, notes, and so on), whereas [class.qual]/3, which brings no ”new normative" value (already covered in [class.qual]/1), is not wrapped as a note or an example.
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.
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.
I think i'm aware of accessibilty but I'm not sure if I understand visibility very clearly
For example:
class X
{
int x;
};
Here, 'x' is only visible in class and but accessible outside of class. If I'm correct, Can someone explain the text in that answer about how visibility is not controlled etc..?
(C++03/11.0) It should be noted that it is access to members and base classes that
is controlled, not their visibility. Names of members are still
visible, and implicit conversions to base classes are still
considered, when those members and base classes are inaccessible. The
interpretation of a given construct is established without regard to
access control. If the interpretation established makes use of
inaccessible member names or base classes, the construct is
ill-formed.
Perhaps this example helps:
class Bob
{
private:
int foo(int, int);
};
class David : Bob
{
void goo() {
int a = foo(1, 2); // #1
}
};
class Dani : Bob
{
void foo();
void goo() {
int a = foo(1, 2); // #2
}
};
On line #1, the name foo is visible, but the function which it names is not accessible (on account of being private to Bob). This is a compilation error, but the compiler knows that there is a potential function Bob::foo that would match, but isn't accessible.
On line #2, the name foo only refers to Dani::foo, while Bob::foo is not visible (because it is hidden), and so there is simply no matching function for the call foo(1, 2). This is also a compilation error, but this time the error is that there is no matching function at all for the call.
C++ has some esoteric feature concerning private class member names visibility and accessibility. By definition, a private class member name is only accessible by the class members and friends. However the rule of visibility can confuse many. They can be summarized as follows.
A private member's name is only accessible to other members and friends.
A private member is visible to all code that sees the class's definition. This means that its parameter types must be declared even if they can never be needed in this translation unit...
Overload resolution happens before accessibility checking.
In C++ today ("C++03" and earlier variants), the notions of accessibility and visibility are
independent. Members of classes and namespaces are visible whenever they are "in
scope" and there is no mechanism to reduce this visibility from the point of declaration.
Accessibility is only a parameter for class members and is orthogonal to the notion of
visibility. This latter observation is frequently surprising to novice C++ programmers. See this PDF.
Consider the following example.
#include < complex>
class Calc
{
public:
double Twice( double d );
private:
int Twice( int i );
std::complex Twice( std::complex c );
};
int main()
{
Calc c;
return c.Twice( 21 ); // error, Twice is inaccessible
}
When the compiler has to resolve the call to a function, it does three main things, in order:
Before doing anything else, the compiler searches for a scope that
has at least one entity named Twice and makes a list of candidates.
In this case, name lookup first looks in the scope of Calc to see if
there is at least one function named Twice; if there isn't, base
classes and enclosing namespaces will be considered in turn, one at a
time, until a scope having at least one candidate is found. In this
case, though, the very first scope the compiler looks in already has
an entity named Twice — in fact, it has three of them, and so that
trio becomes the set of candidates. (For more information about name
lookup in C++, with discussion about how it affects the way you
should package your classes and their interfaces
Next, the compiler performs overload resolution to pick the unique
best match out of the list of candidates. In this case, the argument
is 21, which is an int, and the available overloads take a double, an
int, and a complex. Clearly the int parameter is the best match for
the int argument (it's an exact match and no conversions are
required), and so Twice(int) is selected.
Finally, the compiler performs accessibility checking to determine
whether the selected function can be called.
Note that accessibility (defined by modifiers in C++) and visibility are independent. Visibility is based on the scoping rules of C++. A class member can be visible and inaccessible at the same time.
Static members as an example are visible globally through out the running of your application but accessible only with regard to the modifier applied to them.
As a note: when you declare a class, the scope is private by default (opposed to a struct where members are public by default.)
The variable member 'x' is only accessible by your class and its friends. No one else can ever access 'x' directly (it can indirectly if you have a function returning a reference to it, which is a really bad idea.)
The text you quoted talks about visibility to the compiler, so X::x exists, no matter what. It won't disappear just because it's private. The visibility is used to find the member you are referencing and the first that matches is returned. At that point the compiler checks the accessibility, if accessible, you're all good. If not it is ill-formed.
Note that I mentioned friends. That keyword makes all variable members accessible. When the compiler deals with a friends, it completely ignores all the protected and private keywords.
In most cases, that's a very easy process. It goes in order. Period.
Where it becomes more complicated is when you start using virtual functions: these can be made public, protected, and private and that can change depending on the class declaration... (A derives from B and makes a protected virtual function public; it's generally not a good idea, but C++ doesn't prevent you from doing so.) Of course this only applies to functions, not variable members, so that's a different subject.
That accessability and visibility are independet confuses especially in such situations:
class A
{
public:
void Foo(int i){
}
};
class B : public A
{
private:
void Foo(){
}
};
int main(){
B b{};
b.Foo(12);
}
Programmers from other languages would expect that A::Foo(int) would be callable because it is public. The point here is, that the private B::Foo hides the inherited proc.
This can be solved with a using declaration using A::Foo. But it becomes really hard in this sitation:
class A
{
public:
void Foo(int i){
}
};
class B : public A
{
public:
using A::Foo;
private:
void Foo(){
}
};
class C : public B
{
public:
using B::Foo;
private:
void Foo(char c){
}
}
int main(){
B b{};
b.Foo(12);
}
Using requires that there is NO private function. AFAIK the best way to solve it is to use some prefixes / suffixes for private or protected functions (like do_XXX() or do_XXX_internal).
In the STL private members are usually prefixed by a single underscore (these are reserved identifiers).
In the following example (apologies for the length) I have tried to isolate some unexpected behaviour I've encountered when using nested classes within a class that privately inherits from another. I've often seen statements to the effect that there is nothing special about a nested class compared to an unnested class, but in this example one can see that a nested class (at least according to GCC 4.4) can see the public typedefs of a class that is privately inherited by the closing class.
I appreciate that typdefs are not the same as member data, but I found this behaviour surprising, and I imagine many others would, too. So my question is twofold:
Is this standard behaviour? (a decent explanation of why would be very helpful)
Can one expect it to work on most modern compilers (i.e., how portable is it)?
#include <iostream>
class Base {
typedef int priv_t;
priv_t priv;
public:
typedef int pub_t;
pub_t pub;
Base() : priv(0), pub(1) {}
};
class PubDerived : public Base {
public:
// Not allowed since Base::priv is private
// void foo() {std::cout << priv << "\n";}
class Nested {
// Not allowed since Nested has no access to PubDerived member data
// void foo() {std::cout << pub << "\n";}
// Not allowed since typedef Base::priv_t is private
// void bar() {priv_t x=0; std::cout << x << "\n";}
};
};
class PrivDerived : private Base {
public:
// Allowed since Base::pub is public
void foo() {std::cout << pub << "\n";}
class Nested {
public:
// Works (gcc 4.4 - see below)
void fred() {pub_t x=0; std::cout << x << "\n";}
};
};
int main() {
// Not allowed since typedef Base::priv_t private
// std::cout << PubDerived::priv_t(0) << "\n";
// Allowed since typedef Base::pub_t is inaccessible
std::cout << PubDerived::pub_t(0) << "\n"; // Prints 0
// Not allowed since typedef Base::pub_t is inaccessible
//std::cout << PrivDerived::pub_t(0) << "\n";
// Works (gcc 4.4)
PrivDerived::Nested o;
o.fred(); // Prints 0
return 0;
}
Preface: In the answer below I refer to some differences between C++98 and C++03. However, it turns out that the change I'm talking about haven't made it into the standard yet, so C++03 is not really different from C++98 in that respect (thanks to Johannes for pointing that out). Somehow I was sure I saw it in C++03, but in reality it isn't there. Yet, the issue does indeed exist (see the DR reference in Johannes comment) and some compilers already implement what they probably consider the most reasonable resolution of that issue. So, the references to C++03 in the text below are not correct. Please, interpret the references to C++03 as references to some hypothetical but very likely future specification of this behavior, which some compilers are already trying to implement.
It is important to note that there was a significant change in access rights for nested classes between C++98 and C++03 standards.
In C++98 nested class had no special access rights to the members of enclosing class. It was basically completely independent class, just declared in the scope of the enclosed class. It could only access public members of the enclosing class.
In C++03 nested class was given access rights to the members of the enclosing class as a member of the enclosing class. More precisely, nested class was given the same access rights as a static member function of the enclosing class. I.e. now the nested class can access any members of the enclosing class, including private ones.
For this reason, you might observe the differences between different compilers and versions of the same compiler depending on when they implemented the new specification.
Of course, you have to remember that an object of the nested class is not tied in any way to any specific object of the enclosing class. As far as the actual objects are concerned, these are two independent classes. In order to access the non-static data members or methods of the enclosing class from the nested class you have to have a specific object of the enclosing class. In other words, once again, the nested class indeed behaves as just like a static member function of the enclosing class: it has no specific this pointer for the enclosing class, so it can't access the non-static members of the enclosing class, unless you make an effort to give it a specific object of the enclosing class to access. Without it the nested class can only access typedef-names, enums, and static members of the enclosing class.
A simple example that illustrates the difference between C++98 and C++03 might look as follows
class E {
enum Foo { A };
public:
enum Bar { B };
class I {
Foo i; // OK in C++03, error in C++98
Bar j; // OK in C++03, OK in C++98
};
};
This change is exactly what allows your PrivDerived::Nested::fred function to compile. It wouldn't pass compilation in a pedantic C++98 compiler.
Short answer: Nested classes have access to the containing classes private member in C++0x, but not C++1998 and C++2003. It is however legal for C++98 and C++2003 compilers support the C++0x behavior, since the old behavior is considered a defect.
In the C++98 and 2003 standards section 11.8.1 stated:
The members of a nested class have no
special access to members of an
enclosing class, nor to classes or
functions that have granted friendship
to an enclosing class; the usual
access rules (clause 11) shall be
obeyed. The members of an enclosing
class have no special access to
members of a nested class; the usual
access rules (clause 11) shall be
obeyed.
C++0x section 11.8.1 says:
A nested class is a member and as such
has the same access rights as any
other member. The members of an
enclosing class have no special access
to members of a nested class; the
usual access rules (Clause 11) shall
be obeyed.
Core Language Defect Report 45 shows that the original behavior was considered a defect, so it is legal for non-c++0x compilers to also support the new behavior, although not required.
As per the standard:
9.2 Class members
1 [...] Members of a class are data members, member functions (9.3), nested types,
and enumerators. Data members and member functions are static or non-static; see 9.4.Nested types are
classes (9.1, 9.7) and enumerations (7.2) defined in the class, and arbitrary types declared as members by use
of a typedef declaration (7.1.3).
To answer your questions:
Is this standard behaviour? (a decent explanation of why would be
very helpful)
No. At least with the typedefs not being accessible. However, note that:
class Nested {
// Not allowed since Nested has no access to PubDerived member data
void foo() {std::cout << pub << "\n";}
is problematic. The nested class neither has an instance of PubDerived to work with nor is the pub a static member object.
Can one expect it to work on most modern compilers (i.e., how
portable is it)?
Yes. But do check the documentation for standards compliance. And always: Try out with a few compilers such as Comeau in strict mode.
I've done my best to assemble all the relevant clauses from the ISO/IEC 14882:1997.
Section 9.7:
A class defined within another is called a nested class. The name of a nested class is local to its enclosing class. The nested class is in the scope of its enclosing class. Except by using explicit pointers, references, and object names, declarations in a nested class can use only type names, static members, and enumerators from the enclosing class.
11.2.1 (should be fairly obvious):
[...] If a class is declared to be a base class for another class using the private access specifier, the public and protected members of the base class are accessible as private members of the derived class.
9.9 Nested Type Names:
Type names obey exactly the same scope rules as other names.
Then in 11.8:
The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (11) shall be obeyed. The members of an enclosing class have no special access to members of a nested class; the usual access rules (11) shall be obeyed.
From which I conclude that the behaviour you're experiencing is non-standard. The nested class shouldn't have any 'special' access to the private member of the base.
However, Comeau C++, which seems to have the best standard support, has the same behaviour as GCC (allows fred, disallows bar with "error: type "Base::priv_t" (declared at line 4) is inaccessible").
This doesn't answer your question, but according to my reading of the C++ FAQ Lite 24.6 what you are trying to do isn't allowed. I'm not sure why gcc is permitting it; have you tried it in other compilers as well?