14

I am surprised that in the following example declaring Middle's base class private makes that name unavailable as a type in a subsequent derivation.

class Base {
public:
  Base(Base const& b) : i(b.i) {}

  int i;
};

class Middle : private Base {            //<<<<<<<<<<<
public:
  Middle(Base const* p) : Base(*p) {}
};

class Upper : public Middle {
public:
  Upper(Base const* p) : Middle(p) {}    //<<<<<<<<<<<
};

Compiling thusly with g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516...

g++ -std=c++11 privateBase.cpp

I get the following diagnostics:

privateBase.cpp:15:9: error: ‘class Base Base::Base’ is inaccessible within this context
   Upper(Base const* p) : Middle(p) {}
         ^~~~
privateBase.cpp:1:12: note: declared here
 class Base {
            ^

Clearly at the point that Base was used as Middle's base class its name was available as a type. I can understand that when Base is used to denote base class storage that should be private. But having a declaration of a private base class render a type name inaccessible seems, at the very least, unexpected.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
John Yates
  • 1,027
  • 1
  • 7
  • 18

1 Answers1

16

This is intended; see core issue 175, which even added an example illustrating this in [class.access.spec]p5:

[ 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 ] [ Example:

class A { };
class B : private A { };
class C : public B {
  A* p;             // error: injected-class-name A is inaccessible
  ::A* q;           // OK
};

— end example ]


This falls out of the interaction between class name injection (for rationale, see Why is there an injected class name?) and the fact that in C++ access control applies after name lookup, not before.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 2
    You forgot to explain why. – Robert Harvey Dec 27 '18 at 18:59
  • 3
    @RobertHarvey - is this [cppreference explanation](https://en.cppreference.com/w/cpp/language/injected-class-name) sufficient: "In a class scope, the name of the current class is treated _as if it were a public member name_; this is called injected-class-name. The point of declaration of the name is immediately following the opening brace of the class definition. _Like other members, injected-class-names are inherited._ In the presence of private or protected inheritance, the injected-class-name of an indirect base class might end up being inaccessible in a derived class." (Italics added.) – davidbak Dec 27 '18 at 19:04
  • Yes, that will suffice. – Robert Harvey Dec 27 '18 at 19:06
  • As a guess, the issue is about preventing names from changing when an access specifier changes. Suppose there are both `A` in global scope and `ns::A` in the namespace `ns`, and the base is specified as `ns::A`. In that case, `A` in the derived class refers to `ns::A`. Making the base private shouldn't turn that into `::A`. – Pete Becker Dec 27 '18 at 19:11
  • A related but O.T. question: Are there languages where access control applies during name lookup rather than after? (Just curious. I find access control an interesting part of C++/Java/C#. It seems to be an invention for software engineering purposes as it is not part of O-O theory - at least not so far as _Theory of Objects_ (Abadi, Cardelli) is concerned. And the ur-pure O-O language Smalltalk doesn't have access modifiers...) – davidbak Dec 27 '18 at 19:13
  • @davidbak: Access control is part of encapsulation. It is the very essence of OO. – Robert Harvey Dec 28 '18 at 15:18