C++ Logo

std-discussion

Advanced search

Re: Rules for dynamic_cast and private inheritance

From: Federico Kircheis <federico_at_[hidden]>
Date: Fri, 29 Nov 2024 11:00:29 +0100
On 29/11/2024 10.26, mauro russo wrote:
>
> 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.

Thanks for the clarification.

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

Mhm, I think I'll need to sleep on it, because the standard says

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

In this case

----
auto d = derived();
idevice* id = d.as_idevice();
odevice* iod = dynamic_cast<iodevice*>(id);
----
v would be idevice*, the "most derived object pointed" by it would be 
"derived*", which does not have "a public base class subobject of a C 
object", where C is iodevice*, thus it sounds to me like this rule would 
not apply.
> 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).
Ok, together with the explanation what C is, I think I see now how the 
wording applies for the cast do odevice*:
For the sidecast:
____
[...] 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.
____
In my case, the "most derived object" would be "derived", and since 
derived has no public base class of type C, then the cast fails.
Did I get it right?
> 
>  > 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 ?
In my example, both with "derived" and "middle".
----
#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;}};
void test_device(idevice* id){
     assert(id != nullptr);
     odevice* iod = dynamic_cast<iodevice*>(id);
     assert(iod != nullptr);
     odevice* od = iod;
     assert(od != nullptr);
}
int main(){
     auto d = derived();
     test_device(d.as_idevice());
     auto m = middle();
     test_device(&m);
}
----
you can convert both derived and middle to odevice* with two steps, and 
middle (as shown at the beginning) can be converted in only one step.
>  > ----
>  > 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 10:00:36