4

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().

  1. ??? is Base. x is a public member of Base. In which case there is no issue.
  2. ??? 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.
  3. ??? 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.

AntiElephant
  • 1,227
  • 10
  • 18

1 Answers1

7

Basically, protected inheritance is weird. The reason it compiles is that, from [class.access.base] as of N4527:

A base class B of N is accessible at R, if
— an invented public member of B would be a public member of N, or
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 N, or
R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
— there exists a class S such that B is a base class of S accessible at R and S is a base class of N 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++.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I'm not convinced, see [class.access.base]/4: "A base class B of N is accessible at R, if [...] R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P". – T.C. Oct 12 '15 at 18:53
  • @T.C. You are correct. Clang bug then. Also protected inheritance is weird. – Barry Oct 12 '15 at 18:56
  • The rationale for the resolution of [CWG 1873](http://wg21.link/cwg1873) may apply here though, which would probably make this a defect in the standard. Ugh. – T.C. Oct 12 '15 at 19:02
  • @T.C. So defect report for the defect report, since they just changed member access and not base class access? – Barry Oct 12 '15 at 19:05
  • @T.C. How can you tell if something was adopted or not? The wording from 472 doesn't appear in N4572, for instance. Whereas the other wording appears to have been accepted. – Barry Oct 12 '15 at 19:09
  • @T.C. Also totally rewrote the answer a few times. – Barry Oct 12 '15 at 19:09
  • @Barry 1) the issue status at the top, and 2) the issue list it's in. – T.C. Oct 12 '15 at 19:10
  • @T.C. Oh I see. Thanks. – Barry Oct 12 '15 at 19:11
  • @AntiElephant Basically, the book is correct, the code is valid - though it'll possibly become invalid, and different compilers have different views on its validity. – Barry Oct 12 '15 at 19:14
  • @Barry Thanks a lot. One last thing,when it says the base class is accessible, I'm guessing that means for the Derived-to-Base conversion? Because otherwise I'm sure why `Derived a; cout << a.x` could not work (if the `Base` part of `a` if meant to be accessible). – AntiElephant Oct 12 '15 at 20:49
  • @AntiElephant Accessible as in you can see it. The latter being removed as viable was part of CWG 1873 - there you're just accessing a protected member of a class in a context where you're not a friend. That part is simpler :) – Barry Oct 12 '15 at 20:56