C++ Logo

std-discussion

Advanced search

Re: Rules for dynamic_cast and private inheritance

From: mauro russo <ing.russomauro_at_[hidden]>
Date: Fri, 29 Nov 2024 10:26:29 +0100
Hello, I am a user as you.

> I'm reading the section about dynamic_cast, but I have some difficulties
> understanding the rules, especially when the base class is private.

> Here is the example code I'm dealing with

> ----
> #include <cassert>

> struct device{virtual ~device() = default;};
> struct idevice:device{};
> struct odevice:device{};
> struct iodevice:idevice,odevice{};
> struct middle:iodevice{};
> struct derived:private middle{idevice* as_idevice(){return this;}};
> // or
> // struct derived:private iodevice{idevice* as_idevice(){return this;}};

> void test_device(idevice* id, bool leaf){
> assert(id != nullptr);
> odevice* iod = dynamic_cast<iodevice*>(id);
> assert(iod != nullptr);
> odevice* od = dynamic_cast<odevice*>(id);
> if(leaf){
> assert(od == nullptr);
> }else{
> assert(od != nullptr);
> }
> }

>int main(){
> auto d = derived();
> test_device(d.as_idevice(), true);
> auto m = middle();
> test_device(&m, false);
>}
----
> It works consistently for gcc, clang and msvc, so I assume they are
correct.
> In expr.dynamic.cast, the standard says
> ____
> The result of the expression dynamic_cast<T>(v) [...]
> If C is the class type to which T points or refers, the runtime check
> logically executes as follows:
> If, in the most derived object pointed (referred) to by v, v points
> (refers) to a public base class subobject
> of a C object, and if only one object of type C is derived from the
> subobject pointed (referred) to by v
> the result points (refers) to that C object.
> Otherwise, if v points (refers) to a public base class subobject of the
> most derived object, and the type
> of the most derived object has a base class, of type C, that is
> unambiguous and public, the result points
> (refers) to the C subobject of the most derived object.
> Otherwise, the runtime check fails.
> ___
In your case, when you start from an idevice* that refers
to a base class of a "derived" (your class name) object:
- the first step does not work because v does not point to a
  basic public class of the target type C = odevice,
  which means you are not down-casting.
- the second step, related to side-casting, also does
  not work because odevice is not a public base class
  of the most derived object class "derived"
Second step works for a "middle"(your class name) object, as odevice is
a base public class of "middle", but it is a private base class of
"derived".
A further comment:
Note that the reference to the most derived object even
in the first step, is needed in case the most derived object
contains two sub-objects of the target type C,
that both virtually inherit from the class type to which v points.
e.g.:
class A{};
class B : virtual public A {};
class C : public B {};
class D : public B {};
class E : public C, public D {};
In this case any object E contains two sub-objects B,
but a single sub-object A,
and if you try to dynamically cast from an A* to a B*
when the most derived object is a E object, then
you cannot chose what object B is the target and
the cast fails, despite it is a relation from base to
child class throuh public inheritance.
> Since C does not appear in dynamic_cast<T>(v), and T is a type (thus it
> does not point or refer...), should it maybe be "If C is the class type
> to which v points or refers"?
The sentence means that for example T may be C* or C& .
If you use 'v' instead of 'T', then you will push for 'v' the
constraint to have 'v' already pointing/referring to the
target type, which means you don't need a cast anymore.
> I fail to see why through idevice* a cast to iodevice* always works, but
> a cast to odevice* not.
The relation between idevice and iodevice is from base to child, that
means the first step applies, where it is not required for the target C
(i.e., iodevice) to be a public base class of the most derived object.
Note that when you implicitly cast the iodevice* result from dynamic_cast
to a odevice*, this applies a cast from a child to a base class, which
always works (even dynamic_cast would apply it without requiring
to deal with polymorphic types).
> Both iodevice* and odevice* are not base classes of idevice*, but
> iodevice* is a subclass of idevice*.
> And all those relations are private to derived, but not to idevice and
> iodevice.
Hope the comments above clarified.
> What also seems inconsistent, is that it is still possible to always get
> a odevice* (since iodevice* is a public subclass of odevice*) but one
> needs to do two conversions:
get from what ?
> ----
> idevice* id = /**/;
> assert(id != nullptr);
> odevice* iod = dynamic_cast<iodevice*>(id);
> assert(iod != nullptr);
> odevice* od1 = dynamic_cast<iodevice*>(iod);
Here the result depends on what is the expression used
to initialize iddevice* id, as it might or not might point
to any most derived object.
> // or even without cast
> odevice* od1 = iod;
> assert(od1 != nullptr);
> ----
As explained above, this is from child to base.

Received on 2024-11-29 09:26:42