0

This is a followup related to my previous question where I investigated the problem and found that up- and downcasting seem to be working correctly over public inheritance relations. For example, this code does not even compile:

class A {
};

class B : protected A {
};

int main() {
  B b;
  static_cast<A*>(&b);
};

G++ gives the following error:

t.cc: In function ‘int main()’:
t.cc:10:21: error: ‘A’ is an inaccessible base of ‘B’
   10 |   static_cast<A*>(&b);
      |                     ^

However, I think I found the following trick to overcome this limitation. We can cast inside the class, and then export the casting functionality as a public method:

#include <iostream>

class A {
};

class B : protected A {
  public:
    A* getA() {
      return static_cast<A*>(this);
    };

    static B* fromA(A* a) {
      return static_cast<B*>(a);
    };
};

int main() {
  B b;

  // Does not even compile
  //std::cout << static_cast<A*>(&b);

  // works like charm
  std::cout << b.getA() << '\n';

  // works also in the reverse direction, although it needs a static method
  std::cout << B::fromA(b.getA()) << '\n';
};

I admit it is not very pretty. My tests (in more complex code) show it working, but I am still unsure.

Is it valid C++ code and correct practice?

CatDadCode
  • 58,507
  • 61
  • 212
  • 318
peterh
  • 11,875
  • 18
  • 85
  • 108
  • 2
    Looks fine to me. The fact that `A` is a base class is declared as a `protected` implementation detail, and by making `getA` a member function, this implementation detail does not "leak" to the outside of the class. However, I would rethink my design if I needed this in the first place; implementation inheritance is not often the best tool for the job. – Thomas Aug 05 '20 at 17:23
  • 5
    `protected` base class is one language construct that I have not found a good use for in my 20+ years of programming in C++. – R Sahu Aug 05 '20 at 17:26
  • @RSahu `Base` is a set of animals with a well-defined, narrow & stable set of operations on it. `Derived` is a set of dogs, with a yet more narrowed set of allowed operations on it, added by some extra functionality. You want to write as clean & optimized code as you can. How do you do it? – peterh Aug 05 '20 at 17:31
  • I do not see any benefit. In my mind, `B` either *is* `A`, or not. If it is not, `getA` makes no sense, if it is, it should inherit publicly. – SergeyA Aug 05 '20 at 17:33
  • @SergeyA What if `A` has some methods with unneeded interference with the methods of `B`? I do not want to give out the methods of `A` to the external world, but I want to use them from `B`. – peterh Aug 05 '20 at 17:35
  • But you do, by exposing `getA()`, which immediately gives access to all implementation details of A. – SergeyA Aug 05 '20 at 17:36
  • @SergeyA In this - minimal - example, yes it is so. In practice, the availability of `getA()` can be limited by making it protected/private and extended by friend classes. While the `friend` declaration has no effect to the availability of the base classes. – peterh Aug 05 '20 at 17:38
  • 1
    Well, for a differently structured code my opinion could be different :). – SergeyA Aug 05 '20 at 17:38
  • Body of `getA()` should be simply `return this;`. No cast necessary. – Ben Voigt Aug 05 '20 at 18:23
  • @peterh-ReinstateMonica A `friend` can access protected/private base classes [(example)](https://godbolt.org/z/fb7hdY). Making the conversion function `protected` does not make it more accessible than `protected` inheritance (anything that can access the member function has the protected base visible). – Artyer Aug 05 '20 at 19:42
  • Re the protected inheritance ... I haven't come across a real world use case for that yet either; asked about it here: https://softwareengineering.stackexchange.com/q/414504/65513 – Daniel Jour Aug 05 '20 at 19:45
  • @Artyer Check the output again. gcc-9.3 compiled it, gcc-10.2 gave "inaccessible base" error. The c++ standard says that friendships affect only the local fields, not the parents. – peterh Aug 05 '20 at 19:46
  • @peterh-ReinstateMonica [[class.access.base\]/4](http://eel.is/c++draft/class.access.base#4.2): A base class `B` of `N` is accessible at R if R occurs in a member or friend of class `N`. Also the version was just a mistake on the config of the link, but you can try on multiple versions of different compilers. – Artyer Aug 05 '20 at 19:52
  • @Artyer [Here](https://stackoverflow.com/q/62430684/1783163) my example code shows that it does not, at least casting does not work (externally). – peterh Aug 05 '20 at 20:02
  • @chev Thanks the edit! – peterh Nov 02 '20 at 18:24

1 Answers1

1

Conceptually getA is fine. B is choosing to expose the A in it. It can be simplified to return this;, the cast is unnecessary.

I have reservations about fromA, because it is implying that all As are Bs, which might not be true. You should limit the accessibility of fromA to places where you can prove all the As are Bs.

Safe downcast may be done with dynamic_cast.

cppreference on static_cast

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • I strongly dislike `dynamic_cast`. The essence of C/C++ is to make all testing in compile-time for performance in run-time. – peterh Aug 16 '20 at 17:39