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 correct?
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 inmain
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
, andd.J
accesses inX::f
andg
, hadi
,I
,j
andJ
been overridden inD
in the program).