-1

Here we have a class B, inherited from class A, and it has a friend class C. Being a friend, C should have access to everything in B, including the A base class.

To test it,

  1. first we create a B instance.
  2. we upcast its address to an A*
  3. then we try downcast it with dynamic_cast<> again to B*.

The expected result is to get back the address of the original B instance.

#include <cstdint>
#include <cstdio>

class A {
  public:
    virtual ~A() {};
};

class C;

class B : protected A { // <- this should be public to work! Why?
  friend C;
};

class C {
  public:
    void doit() {
      B *b = new B();
      printf("b= %p\n", b);
      A *a = static_cast<A*>(b);
      printf("a= %p\n", a);
      B *bb = dynamic_cast<B*>(a);
      printf("bb=%p\n", bb);
      delete b;
    };
};

int main() {
  C c;
  c.doit();
  return 0;
};

The common problem in similar cases, that the base class has to be polymorph (which is guaranted here by its empty virtual destructor), is here solved.

However, the dynamic casting still does not work: bb should have the same adress as b.

My experiments show, the only way to make it working, if A is a public base class of B. But... C is a friend of B. It does not work even as protected.

Why is it so?

I use gcc-8, if it matters.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • 3
    Private inheritance means the base class is private, and only accessible to the derived class. – NathanOliver Jun 17 '20 at 13:58
  • 1
    Related: [Why does protected inheritance cause dynamic_cast to fail?](https://stackoverflow.com/questions/12765174) – alter_igel Jun 17 '20 at 13:58
  • friend applies only to members – 463035818_is_not_an_ai Jun 17 '20 at 14:00
  • @NathanOliver It does not work also as `protected`. – peterh Jun 17 '20 at 14:00
  • 2
    It's standard mandated. Probably cause in the end, supporting this will require keeping multiple RTTI records (for different access specifiers). This may be considered undesirable for some. – StoryTeller - Unslander Monica Jun 17 '20 at 14:00
  • 4
    Which is also correct. Protected means only children, grandchildren, ect., classes can access it's members – NathanOliver Jun 17 '20 at 14:01
  • @ThomasSablik I wanted minimal example, and I wanted to print pointer addresses to demonstrate the problem. I will fix it soon, but it won't be minimal any more. – peterh Jun 17 '20 at 14:04
  • [11.9.3#1](https://eel.is/c++draft/class.friend#1) "A friend of a class is a function or class that is given permission to use the private and protected member names from the class." and thats it, no access to private/protected base – 463035818_is_not_an_ai Jun 17 '20 at 14:04
  • @NathanOliver And friends. `C` is a friend of `B`. – peterh Jun 17 '20 at 14:05
  • 3
    No, not and friends. Only derived classes have access. To all others those base class members are private. – NathanOliver Jun 17 '20 at 14:06
  • @idclev463035818 this may be getting off-topic but here is an [online demo](https://coliru.stacked-crooked.com/a/224dfb7a577b0143) of what appears to be a violation of that (interpretation of the) quote from the standard. It appears g++ allows friends to access private base classes and their members. Is g++ wrong? – alter_igel Jun 17 '20 at 14:10
  • @ThomasSablik Ok, fixed. – peterh Jun 17 '20 at 14:11
  • @alterigel I believe private/protected base classes should work as private/protected members. – peterh Jun 17 '20 at 14:12
  • @alterigel actually I never thought about it before, when I looked it up, thats how I interpreted the standard, but that `const Secret& s = a;` seems to show something else – 463035818_is_not_an_ai Jun 17 '20 at 14:13
  • 1
    @alterigel btw I dont think it is offtopic, and I don't quite agree with the duplicate. OP seems to be aware about the difference between public/protected/private inheritance, there is even one answer that claims that friends would see private bases which contradicts the observations (https://stackoverflow.com/a/1374362/4117728). Voting to reopen – 463035818_is_not_an_ai Jun 17 '20 at 14:15
  • 5
    @idclev463035818 @NathanOliver Am I going insane, or are [`private` and `protected` bases indeed accessible from `friend` classes](http://eel.is/c++draft/class#access.base-4.2)? I'd say reason the `dynamic_cast` fails is because the runtime check simply doesn't use the same set of rules as base class accessibility: [it requires the base to be `public`](http://eel.is/c++draft/expr.dynamic.cast#8). Note that you can see this in the code: remove `friend C` and the cast from `B*` to `A*` fails to compile, since `A` is not an accessible base of `B` in `C` unless `C` is a `friend` of `B`. – HTNW Jun 17 '20 at 14:16
  • as [linked by @‌‌‌‌alter_igel](https://stackoverflow.com/questions/62430684/why-c-requires-public-inheritance-ignoring-friend-declarations-to-make-dynam#comment110412870_62430684), this doesn't relate to `friend`, you cannot cast even in `B` itself. – apple apple Apr 18 '23 at 14:38

2 Answers2

1

Think "dynamic_cast" as a global function. "friend C;" gives the friendship to "class C" not to "dynamic_cast".

Member access check is at compile time.

dynamic_cast checks the base-accessibility at Runtime. RTTI has the base-accessibility information (see MS implementation rtti.cpp; __RTDynamicCast), but not the friendship.

  • Thanks! What if I declare in `B` a `friend dynamic_cast()`? – peterh Nov 11 '22 at 11:33
  • @peterh it is not possible. but if it were, same thing as if you were defined a friend function from a extern dll. It is pre compiled, and "friend" has no effect. Of course all that explain here is not the reality, and only to understand it, and to separate compile-time and runtime-time specifications. – Yusuf R. Karagöz Nov 15 '22 at 09:03
0

Summarizing the useful infos and cited resources in the comments, a self-answer can be written.

First, the friend declaration is a no-issue: friend affects only the accessibility of the members of the class where it is declared on, but not its base class(es).

Second, accessibility check in C++ goes in compilation time. But the accessibility check of dynamic_cast happens in runtime. This accessibility check is far more restrictive, and the dynamic_cast can happen only if the inheritance is public.

Its likely reason is that doing it correctly would probably require different rtti tables for the different access levels.

peterh
  • 11,875
  • 18
  • 85
  • 108