SC22/WG21/N1179
J16/99-0002
23 February, 1999
Thomas R Wilcox
trw@rational.com

 

Clarifying the Definition of an Accessible Base Class

This document discusses problems with the specification of the concept of accessibility in the standard and proposes a possible resolution. The discussion is motivated by four items on the core issues list:

  1. Issue 9: Clarification of access to base class members
  2. Issue 16: Access to members of indirect private base classes
  3. Issue 17: Footnote 98 should discuss the naming class when describing members that can be accessed from friends
  4. Issue 19: Clarify protected member access

The major problem with the current text is that the definitions of accessible base classes and accessible member of base classes are interdependent and circular. The definitions of accessible is complex and somewhat non-intuitive. The standard needs to provide a definition that the reader may grind through step-by-step to determine if a member or base class is accessible. Because the published definitions are circular, such a step-by-step procedure is currently not provided.

Contributing to the confusion in clause 11, is the lack of a well-defined term for the accessibility of private members of a class within its derived classes. It would be very helpful if there were a term as concretely defined as the terms public, protected, and private. In the published document, the term "inaccessible" is loosely used to refer to these members. The term is misleading because the private members of a base class are accessible under certain conditions. In fact, the purpose of clause 11.2 is to define precisely when these "inaccessible" members are actually accessible. So, the first proposal is to introduce a specific name for these formerly inaccessible members and define it in the first paragraph of the clause, namely...

-1- A member of a class can be

This wording takes the widely held position that all members of a base class are members of the derived class as well. The revised first paragraph of clause 11 now neatly partitions the set of class members into four, non-overlapping categories related to accessibility.
The next change is to paragraph 1 of clause 11.2. We must elide the four "accessible as" qualifications in this paragraph and just use the accessibility categories defined at the beginning of the clause. We can't use the term "accessible as" here since it really hasn't been defined yet. In fact, we aren't going to define it until paragraph 4. Also, if we add a last sentence to the paragraph to define what happens to private and base private members in the derived class, we make the semantics of the base-class access specifier complete and obviate the need for the rather vague and probably incorrect footnote that is there now. (This resolves issue 17).
Paragraphs 2 and 3 remain unchanged.
Paragraph 4 is where the circular definition of accessible base class and access to base class members is presented. We remove the circularity by first defining accessible base class in terms of the access categories defined at the beginning of the clause. And then define accessibility to base class member in terms of accessible base class.
After these changes, clause 11.2 reads as follows...

11.2 - Accessibility of base classes and base class members [class.access.base]

-1- If a class is declared to be a base class (clause class.derived) for another class using the public access specifier, the public members of the base class are accessible as public members of the derived class and protected members of the base class are accessible as protected members of the derived class. If a class is declared to be a base class for another class using the protected access specifier, the public and protected members of the base class are accessible as protected members of the derived class. 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*. In all cases, the private and base private members of the base class are base private members of the derived class regardless of the access specifier used to declare the base class.

[Footnote: As specified previously in clause class.access, private members of a base class remain are inaccessible even to a derived classes unless friend declarations within its the base class declarations are used to grant it access explicitly. --- end foonote]

-2- In the absence of an access-specifier for a base class, public is assumed when the derived class is declared struct and private is assumed when the class is declared class. [Example:

class B { /* ... */ };
class D1 : private B { /* ... */ };
class D2 : public B { /* ... */ };
class D3 : B { /* ... */ };     //   B  private by default
struct D4 : public B { /* ... */ };
struct D5 : private B { /* ... */ };
struct D6 : B { /* ... */ };    //   B  public by default
class D7 : protected B { /* ... */ };
struct D8 : protected B { /* ... */ };

Here B is a public base of D2, D4, and D6, a private base of D1, D3, and D5, and a protected base of D7 and D8.
--- end example]

-3- [Note: A member of a private base class might be inaccessible as an inherited member name, but accessible directly. Because of the rules on pointer conversions (conv.ptr) and explicit casts (expr.cast), a conversion from a pointer to a derived class to a pointer to an inaccessible base class might be ill-formed if an implicit conversion is used, but well-formed if an explicit cast is used. For example,

class B {
public:
	int mi;                 //  nonstatic member
	static int si;          //  static member
};
class D : private B {
};
class DD : public D {
	void f();
};
void DD::f() {
	mi = 3;                 //  error:  mi  is private in  D
	si = 3;                 //  error:  si  is private in  D
	B  b;
	b.mi = 3;               //  OK (b.mi  is different from  this->mi)
	b.si = 3;               //  OK (b.si  is different from  this->si)
	B::si = 3;              //  OK
	B* bp1 = this;          //  error:  B  is a private base class
	B* bp2 = (B*)this;      //  OK with cast
	bp2->mi = 3;            //  OK: access through a pointer to  B.
}


--- end note]

-4- A base class B of N is accessible at R, if

class B {
public:
  int m;
};
class S : private B {
  friend class N;
};
class N : private S {
  void f() {
    B* p = this;   // OK: Because class S satisfies the fourth condition above...
                   // B is a base class of S accessible in f because f is a friend of S and m is private in S
                   // and S is a base class of N accessible in f because f is a member of N and a public
                   // member of S would be private in N.
  }
};

--- end example]

-5- A base class is said to be accessible if an invented public member of the base class is accessible. If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class (conv.ptr, conv.mem). [Note: it follows that members and friends of a class X can implicitly convert an X* to a pointer to a private or protected immediate base class of X. ] The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found. [Note: this class can be explicit, e.g., when a qualified-id is used, or implicit, e.g., when a class member access operator (expr.ref) is used (including cases where an implicit ``this->'' is added). If both a class member access operator and a qualified-id are used to name the member (as in p->T::m), the class naming the member is the class named by the nested-name-specifier of the qualified-id (that is, T). If the member m is accessible when named in the naming class according to the rules below, the access to m is nonetheless ill-formed if the type of p cannot be implicitly converted to type T (for example, if T is an inaccessible base class of p's class). ]

 A member m is accessible at the point R when named in class N if 
class B;
class A {
private:
  int i;
  friend void f(B*);
};
class B : public A { };
void f(B* p) {
  p->i = 1;                     //  OK:  B*  can be implicitly cast to  A*,
				//  and  f  has access to  i  in  A
}


--- end example]

------------------------------------------------------------

[Discussion of issues....

Issue 9 Example:
    class D;
 
    class B
    {
     protected:
       int b1;
 
       friend void foo( D* pd );
    };
 
    class D : protected B { };
 
    void foo( D* pd )
    {
       if ( pd->b1 > 0 ); // Is 'b1' accessible?
    }

Analysis: Is b1 accessible?

Issue 16 Example:

  class D;
 
  class B {
  private:
      int i;
      friend class D;
  };
 
  class C : private B { };
 
  class D : private C {
      void f() {
          B::i; //1: well-formed?
          i;    //2: well-formed?
      }
  };

(A) Is i accessible in f when named in B?

(B) Is i accessible in f when named in D?

So, in both these cases, friendship with the base class does not grant access to the private member because the base class is not accessible through two levels of private base classes. If, however, B were merely a protected base of C rather than a private base of C, then i would be accessible within f because B would be an accessible base of D within f (f is a member of D and a public member of B would be a private member of D) and it has already been established that i is accessible within f when named in B. The B:: qualification is redundant in this case.