Search code examples
c++inheritancelanguage-lawyerfriend

How does friend class of the base class access members of that base class through objects of class derived from the base class?


Here is my codes in file source.cpp:

class B
{
  friend class F;
protected:
  int protectedIntB;
};

class D : public B {};

class F
{
public:
  int f(D &d) {return ++d.protectedIntB;}
};

When I compile above codes with g++ -c -Wall -pedantic -std=c++11 source.cpp and cl /c source.cpp, both compilers compile successfully. However, when I make D inherits from B using protected instead of public:

class D : protected B {};

This time, gcc compiles successfully while cl gives an error says that B::protectedIntB is inaccessible in return ++d.protectedIntB;.

Another situation is replacing public with private:

class D : private B {};

This time, both compilers yield errors. By the way I'm using gcc version 5.3.0 built by mingw-w64 and cl version 19.00.24210 from VS2015.

Here comes my question:

How does friend class of the base class access members of that base class through objects of class derived from the base class, and why gcc and cl handle it differently?

Edit:

Thanks to songyuanyao and Brian, it seems a bug in gcc 5.3.0 in the protected case. Only the public case should be compiled successfully, and gcc 6.1.0 also works fine.


Solution

  • According to [class.access.base]/5:

    The access to a member is affected by the class in which the member is named. This naming class is the class in which the member name was looked up and found.

    According to [class.access.base]/6:

    If a class member access operator, including an implicit “this->,” is used to access a non-static data member or non-static member function, the reference is ill-formed if the left operand (considered as a pointer in the “.” operator case) cannot be implicitly converted to a pointer to the naming class of the right operand.

    Therefore, in your particular case, in order to access d.protectedIntB, both of the following must be true:

    • You have to have access to protectedIntB as a member of B, since B is the class in which the name protectedIntB was found. (Note: this can be altered by redeclaring the member in the derived class using a using-declaration; in that case the derived class would be controlling.)

    • You have to have access to B as a base of D, i.e., be able to convert D* to B*. If B is a public base of D, fine. If B is a protected base of D, an access check applies, which F::f fails since F is not a friend of D and is not a derived class of D.

    Surprisingly, it seems that GCC is the compiler that's wrong in the protected case but this bug appears fixed. Note that Clang gives a much better diagnostic.