views:

439

answers:

4

First time on StackOverflow, so please be tolerant.

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 threefold:

  1. Is this standard behaviour? (a decent explanation of why would be very helpful)
  2. 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;
}
+1  A: 

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:

  1. 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.

  1. 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.

dirkgently
+1 Good catch on PubDerived::Nested::foo(). Regarding the pointer to the standard, it wasn't clear to me that there was any mention of what a nested class could see (p00ya's answer found the important clause, I think).
beldaz
A: 

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?

fbrereto
Only have GCC to hand, which was why I was interested in the code's portability. The linked FAQ entry doesn't mention nested classes so far as I can see.
beldaz
+1  A: 

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").

p00ya
The quotes you provided are from C++98 specification. This was reworked in C++03, which is why you will see different behavior in Comeau, and more recent versions of GCC and MSVC++ compilers.
AndreyT
+1 for references to the standard (AndreyT's comment also noted). They back up what I was intuitively expecting.
beldaz
Yeah none of what I said can be assumed to apply to C++03 (but I don't have a copy of that spec unfortunately).
p00ya
+4  A: 

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.

AndreyT
*"difference between C++89"* -> *"difference between C++98"*
Georg Fritzsche
@gf: Fixed. Thank you.
AndreyT
Just what I was after, thanks. I think I shall throw in a `using` statement to the code I am working on to ensure compatibility with the 98 standard.
beldaz
You've explained why the nested class has access in C++03, but not the difference between public and private inheritance. Why is it that compilers (presumably C++03 compliant) don't allow access to the enclosing class's public inherited base types (e.g. in `bar` in the OP), but do with private inheritance (`fred`)?
p00ya
@p00ya: Type `priv_t` is *private* in `Base`. There's no way to "un-private" something that is already private, regardless of the inheritance type (the fcat that `Base` is inherited publically makes no difference). Nobody can access `priv_t` besides `Base` itself. That's why `bar` can't access it. It really has nothing to do with nested classes at all.
AndreyT
@AndreyT, C++03 still has these broken rules. It says "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. ". I think it is only C++0x that fixes this.
Johannes Schaub - litb
(Notice http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#45 , which hasn't status TC1).
Johannes Schaub - litb
@AndreyT: oops, I had assumed that `bar` and `fred` were both using `pub_t` to be analogous. I see now that `bar` is using `priv_t`.
p00ya