All standard references below refer, unless noted otherwise, to N4861 (March 2020 post-Prague working draft/C++20 DIS).
Background
According to [class.access.base]/5:
If a base class is accessible, one can implicitly convert a pointer to a derived class to a pointer to that base class [...].
Meaning the following example is well-formed:
class N {};
class P : private N {
friend void f();
};
void f() {
P p{};
N* n = &p; // R: OK as per [class.access.base]/5
}
as N is an accessible base class at R above(+).
[class.access.base]/5 also mentions that [emphasis mine]:
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: [...] 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 denoted by the nested-name-specifier of the qualified-id (that is,T). — end note ]
and [emphasis mine]:
A member
mis accessible at the pointRwhen named in classNif
- [...]
- /5.3
mas a member ofNis protected, andRoccurs in a member or friend of classN, or in a member of a classPderived fromN, wheremas a member ofPis public, private, or protected, or- /5.4 there exists a base class
BofNthat is accessible atR, andmis accessible atRwhen named in classB.
With that in mind, consider the following example:
class N {
protected:
int m;
};
class P : private N {
friend void f();
};
void f() {
P p{};
(&p)->N::m = 42; // R: #1
}
where as per above the naming class at #1 is N. The example is accepted by both Clang and GCC, for various compiler versions and standards, meaning it's arguably well-formed.
It would seems as if &p (which is of type P*) is implicitly converted to N* (fulfilling [class.access.base]/6), but I'm wondering by what rules the member m of N (N being the naming class) is accessible at an R which is a friend to a derived class P of N.
Question
- What rule(s) governs that
#1is well-formed?
As per above, the naming class at #1 is N, but [class.access.base]/5.3 should not apply as R is in a friend of class P derived from N (/5.3 only mentions in a member of class P). [class.access.base]/5.4 should not apply as the naming class is N which is the top-level class in the class hierarchy.
We may note that [class.protected]/1 mentions the very example above as well-formed as part of the non-normative example block of the paragraph. However, [class.protected]/1 in its entirety is described as
An additional access check [...]
arguably meaning [class.access.base] still needs to apply; it particularly seems as if [class.access.base]/5.3 is arguably missing mentioning the case "or a friend of class P" which [class.protected]/1 shows in an (non-normative) example.
(+) An accessible base class
In the following example:
class B { };
class N : B {
friend void f();
};
void f() { /* R */ }
according to [class.access.base]/4, specifically [class.access.base]/4.2 [emphasis mine]:
A base class
BofNis accessible atR, if
- /4.1 [...]
- /4.2
Roccurs in a member or friend of classN, and an invented public member ofBwould be a private or protected member ofP, [...]
B is accessible at R, namely in the friend f of N.