dfrib
dfrib

Reputation: 73186

Member access rules for friend of derived class, where the naming class is the base class

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 m is accessible at the point R when named in class N if

  • [...]
  • /5.3 m as a member of N is protected, and R occurs in a member or friend of class N, or in a member of a class P derived from N, where m as a member of P is public, private, or protected, or
  • /5.4 there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B.

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

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 B of N is accessible at R, if

  • /4.1 [...]
  • /4.2 R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of P, [...]

B is accessible at R, namely in the friend f of N.

Upvotes: 6

Views: 185

Answers (1)

xmh0511
xmh0511

Reputation: 7369

Actually, the section [class.protected#1] is an additional clause for [class.access.base#5] when the nominated member is a protected member of the naming class to which R occurs at a member or friend of a derived class.
According to class.access.base#1, the non-static protected member of N is accessible as a private member of the derived class P. We are permitted to access the member m in a friend of P as per class.access.base#5.2, if the naming class is P

m as a member of N is private, and R occurs in a member or friend of class N, or

Back to [class.protected#1], we should take a look at the following rule:

An additional access check beyond those described earlier in Clause [class.access] is applied when a non-static data member or non-static member function is a protected member of its naming class ([class.access.base])115 As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C.

In other words, the rule says that only if these conditions are satisfied will the additional rule be applied. That is:

  1. The member should first be a non-static member(data or function)
  2. The member should be a protected member of the naming class.

In order to make the additional rule apply, the following conditions should also be satisfied

  1. As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C.
  2. If the access is to form a pointer to member ([expr.unary.op]), the nested-name-specifier shall denote C or a class derived from C.
  3. All other accesses involve a (possibly implicit) object expression. In this case, the class of the object expression shall be C or a class derived from C.

Condition 3 might have some confusion, however, it does not say C must be the naming class or not. It just says that the member can be accessible in a member or friend of C.

After sorting out these conditions, we could take a look at the example

void f()  {
    P p{};
    (&p)->N::m = 42;  // R: #1
}

The naming class is N and the non-static member m is a protected of N, hence conditions 1 and 2 are true.

m as a private member is accessible in a friend of P, hence condition 3 is true.

(&p)->N::m is a class member access expression where its object expression is of type P, hence condition 5 is true. So, such an expression is well-formed when it occurs in f.

If change the non-static member m to be a static member, then condition 1 is false, which means the additional rule will not apply to the expression.

In simple, [class.protected#1] add an extra option(that is a friend) for the latter bullet of [class.access.base#5.2], if these conditions are satisfied. Moreover, it also restricts these members that might be accessible as per the latter bullet of [class.access.base#5.2] to turn non-accessible members when they satisfy these conditions.

Upvotes: 2

Related Questions