为什么protected成员不能被derived 类的朋友使用?

Why can’t protected members be used by friends of derived classes?

C++ 标准在 [class.access/1] 中声明(强调我的):

A member of a class can be

  • private; that is, its name can be used only by members and friends of the class in which it is declared.
  • protected; that is, its name can be used only by members and friends of the class in which it is declared, by classes derived from that class, and by their friends (see [class.protected]).
  • public; that is, its name can be used anywhere without access restriction.

那么为什么编译器会在以下 C++ 程序中引发此错误?

#include <iostream>

class B {
  protected:
    static int const i = 1;
};

class D: public B {
  public:
    void f();
    friend void g();
};

void D::f() {
  B b;
  std::cout << b.i;  // OK
}

void g() {
  B b;
  std::cout << b.i;  // error: 'i' is a protected member of 'B'
}

int main() {
  D d;
  d.f();
  g();
  return 0;
}

请注意,受保护的数据成员 B::i 被声明为 static 不受特定于 protected non-static[= 的进一步限制[class.access/class.protected-1] 中的 30=] 成员也会在 D::f 成员函数中的 b.i 访问中引发与 g 函数中相同的错误。

注意。 — 我在 Clang 9.0.0 编译器上使用 C++ 17。

这似乎是一个规格问题。我引用的部分与另一部分不同步,该部分指定了 Clang 之后的新的正确行为(但令人惊讶的是 GCC 和 MSVC 都没有),[class.access/base-5](强调我的):

A member m is accessible at the point R when named in class N if

  • m as a member of N is public, or
  • m as a member of N is private, and R occurs in a member or friend of class N, or
  • 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
  • 存在 N 的基 class B,可在 R 访问,并且 m 在 class B 中命名时可在 R 访问。

这里friend没有提到派生class。但它曾经是。由于缺陷报告 CWG 1873(强调我的),它已在 C++ 17 中删除:

Protected member access from derived class friends

Section: 14.2 [class.access.base] Status: CD4 Submitter: Richard Smith Date: 2014-02-18

[Moved to DR at the May, 2015 meeting.]

According to 14.2 [class.access.base] paragraph 5,

A member m is accessible at the point R when named in class N if

  • m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is public, private, or protected, or

The granting of access via class P is troubling. At the least, there should be a restriction that P be visible at R. Alternatively, this portion of the rule could be removed altogether; this provision does not appear to be widely used in existing code and such references can be easily converted to use P instead of N for naming the member.

Notes from the June, 2014 meeting:

CWG noted that removing the friend provision would introduce an undesirable asymmetry between member functions of P and its friends. Instead, the intent is to require P to be a complete type at R.

Notes from the November, 2014 meeting:

Although the asymmetry is unfortunate, the difference between a reference in a member function and a reference in a friend is that the member function concretely determines which P is in view, while the friend could be befriended by a class that is a specialization of a class template and thus would require instantiations that would not otherwise occur. CWG thus decided simply to eliminate the friend case.

Proposed resolution, November, 2014:

Change bullet 5.3 of 14.2 [class.access.base] as follows:

A member m is accessible at the point R when named in class N if

  • m as a member of N is protected, and R occurs in a member or friend of class N, or in a member or friend of a class P derived from N, where m as a member of P is public, private, or protected, or
  • there exists…

我已在 GitHub 上提交了拉取请求以更正此不一致:https://github.com/cplusplus/draft/pull/3672