Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Derived-to-base conversion and friendship confusion

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.

like image 575
AntiElephant Avatar asked Mar 14 '23 12:03

AntiElephant


1 Answers

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++.

like image 146
Barry Avatar answered Mar 23 '23 23:03

Barry