派生 class 的朋友的成员访问规则,其中命名 class 是基础 class

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

除非另有说明,否则以下所有标准参考均指 N4861 (March 2020 post-Prague working draft/C++20 DIS)


背景

根据[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 [...].

表示以下示例格式正确:

class N {};

class P : private N {
    friend void f();
};

void f()  { 
    P p{};
    N* n = &p; // R: OK as per [class.access.base]/5
}

因为 N 是可访问的基础 class 在 R 以上 (+).

[class.access.base]/5 还提到 [强调 我的]:

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 ]

和[强调我的]:

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.

考虑到这一点,请考虑以下示例:

class N {
  protected:
    int m;
};

class P : private N {
    friend void f();
};

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

根据上面的 命名 class#1N。该示例被 Clang 和 GCC 接受,适用于各种编译器版本和标准,这意味着它可以说是格式良好的。

似乎 &p(属于 P* 类型)隐式转换为 N*(满足 [class.access.base]/6),但我想知道什么规则 N 的成员 mN 是命名 class)可在 R 访问,该 R 是派生 [=148= 的朋友] PN

问题

根据上述,命名 class#1N,但 [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 不应适用,因为命名 class 是 N,这是 class 层次结构中的顶级 class。

我们可能会注意到 [class.protected]/1 提到上面的示例作为该段落的非规范示例块的一部分格式良好。然而,[class.protected]/1 整体被描述为

An additional access check [...]

可以说是[class.access.base]仍然需要申请;特别是 [class.access.base]/5.3 似乎缺少提及案例“或 class P 的朋友”,[class.protected]/1 在 (非规范)示例。


(+) 可访问的基础 class

在下面的例子中:

class B { };

class N : B {
    friend void f();
};

void f()  { /* R */ }

根据[class.access.base]/4, specifically [class.access.base]/4.2[强调我的]:

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 可在 R 访问,即 N 的朋友 f

实际上,[class.protected#1] 部分是 [class.access.base#5] 的附加条款,当指定成员是 命名 class 其中 R 出现在派生 class.
的成员或朋友处 根据class.access.base#1N 的非静态受保护成员可作为派生 class P 的私有成员访问。如果命名 class 是 P

,我们可以根据 class.access.base#5.2 访问 P 的朋友中的成员 m

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

回到[class.protected#1],我们应该看看下面的规则:

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.

换句话说,规则说只有满足这些条件才会应用附加规则。即:

  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.

为了使附加规则适用,还应满足以下条件

  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.

条件3可能有些混淆,但是,它并没有说C一定是命名class或不是。它只是说该成员可以在 C.

的成员或朋友中访问

整理完这些条件,我们可以看一下例子

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

命名 class 是 N,非静态成员 mN 的受保护成员,因此条件 1 和条件 2 成立。

m 因为私有成员可以在 P 的好友中访问,因此条件 3 成立。

(&p)->N::m 是一个 class 成员访问表达式,其对象表达式的类型为 P,因此条件 5 为真。因此,当出现在 f.

中时,这样的表达式是合式的

如果将非静态成员 m 更改为静态成员,则条件 1 为假,这意味着附加规则将不适用于表达式。

简单来说,如果满足这些条件,[class.protected#1]为[class.access.base#5.2]的后一个项目符号添加一个额外的选项(即朋友)。此外,它还限制了这些根据[class.access.base#5.2]的后一条可以访问的成员在满足这些条件时变为不可访问的成员。