This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 118c. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2025-10-11
[Voted into WP at October 2004 meeting.]
We consider it not unreasonable to do the following
class A {
protected:
void g();
};
class B : public A {
public:
using A::g; // B::g is a public synonym for A::g
};
class C: public A {
void foo();
};
void C::foo() {
B b;
b.g();
}
However the EDG front-end does not like and gives the error
#410-D: protected function "A::g" is not accessible through a "B" pointer or object
b.g();
^
Steve Adamczyk: The error in this case is due to 11.8.5 [class.protected] of the standard, which is an additional check on top of the other access checking. When that section says "a protected nonstatic member function ... of a base class" it doesn't indicate whether the fact that there is a using-declaration is relevant. I'd say the current wording taken at face value would suggest that the error is correct -- the function is protected, even if the using-declaration for it makes it accessible as a public function. But I'm quite sure the wording in 11.8.5 [class.protected] was written before using-declarations were invented and has not been reviewed since for consistency with that addition.
Notes from April 2003 meeting:
We agreed that the example should be allowed.
Proposed resolution (April 2003, revised October 2003):
Change 11.8.5 [class.protected] paragraph 1 from
When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in 11.8 [class.access]. [Footnote: This additional check does not apply to other members, e.g. static data members or enumerator member constants.] Except when forming a pointer to member (7.6.2.2 [expr.unary.op]), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class (7.6.1.5 [expr.ref]). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).
to
An additional access check beyond those described earlier in 11.8 [class.access] is applied when a nonstatic data member or nonstatic member function is a protected member of its naming class (11.8.3 [class.access.base]). [Footnote: This additional check does not apply to other members, e.g., static data members or enumerator member constants.] As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (7.6.2.2 [expr.unary.op]), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (7.6.1.5 [expr.ref]). In this case, the class of the object expression shall be C or a class derived from C.
Additional discussion (September, 2004):
Steve Adamczyk: I wonder if this wording is incorrect. Consider:
class A {
public:
int p;
};
class B : protected A {
// p is a protected member of B
};
class C : public B {
friend void fr();
};
void fr() {
B *pb = new B;
pb->p = 1; // Access okay? Naming class is B, p is a protected member of B,
// the "C" of the issue 385 wording is C, but access is not via
// an object of type C or a derived class thereof.
}
I think the formulation that the member is a protected member of its naming class is not what we want. I think we intended that the member is protected in the declaration that is found, where the declaration found might be a using-declaration.
Mike Miller: I think the proposed wording makes the access pb->p ill-formed, and I think that's the right thing to do.
First, protected inheritance of A by B means that B intends the public and protected members of A to be part of B's implementation, available to B's descendants only. (That's why there's a restriction on converting from B* to A*, to enforce B's intention on the use of members of A.) Consequently, I see no difference in access policy between your example and
class B {
protected:
int p;
};
Second, the reason we have this rule is that C's use of inherited protected members might be different from their use in a sibling class, say D. Thus members and friends of C can only use B::p in a manner consistent with C's usage, i.e., in C or derived-from-C objects. If we rewrote your example slightly,
class D: public B { };
void fr(B* pb) {
pb->p = 1;
}
void g() {
fr(new D);
}
it's clear that the intent of this rule is broken — fr would be accessing B::p assuming C's policies when the object in question actually required D's policies.
(See also issues 471 and 472.)