From C++ Primer 5th edition (D inherits from B)
Member functions and friends of classes derived from D may use the derived-tobase conversion if D inherits from B using either public or protected. Such code may not use the conversion if D inherits privately from B.
Is there any reason for this or am I meant to take it at face value? It might seem obvious why this is but it's tripping me up in one example:
#include <iostream>
using namespace std;
class Base {
public:
int x = 2;
};
class Derived : protected Base { };
class DerivedAgain : public Derived {
friend void test();
};
void test() {
??? a;
Base* p = &a;
cout << p->x;
}
int main(){
test();
}
I want to understand the accessibility of test()
to the member x
in derived-to-base conversions. Considering the three potential cases of the type ???
of a
in function test()
.
???
is Base
. x
is a public member of Base
. In which case there is no issue.???
is DerivedAgain
. In which case the Derived-to-Base conversion makes sense since test()
has friend
access to all members of DerivedAgain
, including those inherited indirectly from Base
. So there is no issue using a pointer to access x
.???
is Derived
. It compiles fine. But why? I'm confused at this point. test()
doesn't have special access to the members of a Derived
class, so why should p->x
work and consequently the Derived-to-Base conversion be valid? Does it work just cause?Indeed if I change the operation of test()
to
void test() {
Derived a;
cout << a.x;
}
It does not compile, as I would expect to happen - because the x
member that a Derived
object inherits is made protected
and thus can't be used by users.
If I replace the type of a
with Base
and DerivedAgain
the modified test()
compiles fine, as I would expect it to.
I'm just confused as to why a friend function of a second-level derived class is allowed to use the first-level direct-to-base conversion, if that friend function has no special access to the first-level derived class members.
Basically, protected inheritance is weird. The reason it compiles is that, from [class.access.base] as of N4527:
A base class
B
ofN
is accessible at R, if
— an invented public member ofB
would be a public member ofN
, or
— R occurs in a member or friend of classN
, and an invented public member ofB
would be a private or protected member ofN
, or
— R occurs in a member or friend of a classP
derived fromN
, and an invented public member ofB
would be a private or protected member ofP
, or
— there exists a classS
such thatB
is a base class ofS
accessible at R andS
is a base class ofN
accessible at R.
The third bullet point here is the relevant one. R occurs in a friend (test
) of a class P
(DerivedAgain
) derived from N
(Derived
), and an invented public member of B
(Base
) would be a protected member of P
(DerivedAgain
).
I had previously believed accepting this code to be a gcc bug (bug 67493), but now I believe that failing to accept it is a clang bug - although as T.C. additionally points out, there is a relevant standard defect (CWG #1873). The wording change there applied only to member access, whereas what's relevant to us here is base access. But perhaps gcc is simply implementing the rule of the standard (acceptance is correct) whereas clang is following with the logical conclusion of this defect report (currently active CWG #472) and just disallowing it.
Again, protected inheritance is really weird. Welcome to the wonderful world of C++.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With