Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[class.access] Eliminate the friend case for protected member access from derived class #3672

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

geryogam
Copy link
Contributor

This PR will fix an inconsistency in the specification described here:
https://stackoverflow.com/q/60178872/2326961

@zygoloid
Copy link
Member

zygoloid commented Mar 5, 2020

Hmm. It's unclear whether [class.access]p1 is abstractly talking about a direct member of a class, or about a member of a class when named as a member of some (possibly) different class. We should clarify. I think it probably does mean the latter, which would make this change correct.

Maybe adding a note:

[Note: A member of a base class is also a member of a derived class, and the same entity may have different access in each class in which it is a member.]

would help?

@@ -4472,8 +4472,8 @@
\indextext{access control!\idxcode{protected}}%
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~\ref{class.protected}).
of the class in which it is declared, and members of classes derived from that class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"... and by members of classes derived..." (add "by" to keep the sentence structure manageable)

@@ -4472,8 +4472,8 @@
\indextext{access control!\idxcode{protected}}%
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~\ref{class.protected}).
of the class in which it is declared, and members of classes derived from that class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more concern here: We change "by classes" to "by members of classes". This change seems to exclude the base-specifier, which is not in a member. Example:

struct B { };
struct D1 : protected B { };
struct D2 : D1 {};

I think the injected-class-name B is protected in D1, thus accessible from D2. But the name doesn't appear in a member.

That said, this paragraph is just one of those intro sentences where the actual rules appear later. (Note the funny punctuation, btw.) So, we shouldn't say wrong things, but we don't need to be ultra-precise either.

@jensmaurer
Copy link
Member

@zygoloid , I'm not in favor of adding a note here. This is an intro sentence, so let's keep it as vague as we possibly can.

@geryogam
Copy link
Contributor Author

geryogam commented Mar 5, 2020

@zygoloid @jensmaurer Actually I opened this PR a bit too quickly. After thinking about member access rules more, I realized that the root of the problem in [class.access/1] is that the paragraph mixes direct access (correctly described in [class.access/base-5.1], [class.access/base-5.2], [class.access/base-5.3], [class.access/base-5.4] and [class.access/class.protected-1]) and inheritance access (correctly described in [class.access/base-1]). It should either talk about one or the other, not both at the same time. So I plan to open a better PR than this one (or update this one) in the following days since this PR did not solve the real problem. I published my thoughts in this Stack Overflow post:

I am trying to fully understand member access rules defined in multiple paragraphs of the [class.access] section of the C++ standard. They are quite complex or even confusing therefore I need a short but accurate and exhaustive summary.

I compiled this program to test the accessibility of protected members in several situations (since the rules for protected members are the most complex):1

#include <iostream>

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

class X: public B {
  protected:
    int j = 2;
    static int const J = 2;
  public:
    void f();
    friend void g();
};

class D: public X {
  protected:
    int k = 3;
    static int const K = 3;
};

void X::f() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  std::cout << b.I;
  std::cout << x.i;
  std::cout << x.I;
  std::cout << x.j;
  std::cout << x.J;
  std::cout << d.i;
  std::cout << d.I;
  std::cout << d.j;
  std::cout << d.J;
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
}

void g() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  //std::cout << b.I;  // error: 'I' is a protected member of 'B'
  std::cout << x.i;
  std::cout << x.I;
  std::cout << x.j;
  std::cout << x.J;
  std::cout << d.i;
  std::cout << d.I;
  std::cout << d.j;
  std::cout << d.J;
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
}

int main() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  //std::cout << b.I;  // error: 'I' is a protected member of 'B'
  //std::cout << x.i;  // error: 'i' is a protected member of 'B'
  //std::cout << x.I;  // error: 'I' is a protected member of 'B'
  //std::cout << x.j;  // error: 'j' is a protected member of 'X'
  //std::cout << x.J;  // error: 'J' is a protected member of 'X'
  //std::cout << d.i;  // error: 'i' is a protected member of 'B'
  //std::cout << d.I;  // error: 'I' is a protected member of 'B'
  //std::cout << d.j;  // error: 'j' is a protected member of 'X'
  //std::cout << d.J;  // error: 'J' is a protected member of 'X'
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
  return 0;
}

I came to this conclusion on direct accessibility:2

  • public members of a class are directly accessible to any entities (cf. [class.access/base-5.1]);
  • private members of a class are directly accessible only to members and friends of that class (cf. [class.access/base-5.2]);
  • protected members of a class are directly accessible only to members and friends of that class (cf. [class.access/base-5.3]), to members and friends of the base classes of that class if those protected members are inherited from those base classes or the base classes of those base classes (cf. [class.access/base-5.4]),3 and to members of the derived classes of that class (cf. [class.access/base-5.3]) if those protected members are not non-static data members nor non-static member functions (cf. [class.access/class.protected-1]).

Is my summary accurate and exhaustive?


1 I used the Clang 9.0.0 compiler with C++ 17.

2 Access to a member i of a class B can be either direct, that is through that class: b.i (direct access), or indirect, that is through a derived class D of that class: d.i (inheritance access). Since members inherited by a derived class are members of that derived class with changes in their accessibility (cf. [class.access/base-1]), inheritance access to a member of a class can be treated as direct access to the inherited member of the derived class of that class. In other words, only direct access needs to be considered.

3 My clause here differs slightly from the referenced clause of the standard [class.access/base-5.4]:

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

  • there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B.

This is intended as the compiler behaves differently and my feeling is that the compiler is right. In my opinion there are two problems with the clause of the standard:

  • the access point R should be restricted to members and friends of class B (which is what the compiler does by raising errors for the d.* accesses in main in the program);
  • the member m in class N should be restricted to be inherited from class B, not overridden by class N (which is what the compiler would have done by raising errors for the d.i, d.I, d.j, and d.J accesses in X::f and g, had i, I, j and J been overridden in D in the program).

@zygoloid zygoloid added the cwg Issue must be reviewed by CWG. label Mar 12, 2020
@zygoloid
Copy link
Member

This is too far into the technical details to address without CWG oversight.

@tkoeppe
Copy link
Contributor

tkoeppe commented Nov 12, 2023

Could you please rebase?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cwg Issue must be reviewed by CWG. needs rebase The pull request needs a git rebase to resolve merge conflicts.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants