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 14:02:06 +0100
> 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.

>From idevice* to iodevice*, the first part of the rule works.
Indeed, you previously wondered why the (down-) cast
toward iodevice always worked.
Here you have that C = iodevice whereas v points to an idevice,
therefor you need idevice to be a public base class of iodevice,
which is true, therefore the rule works. This is the down-cast.
Moreover, the most derived object does not have multiple
iodevice sub-objects having the idevice object input as
nested sub-objects (recall the example with the virtual
inheritance in my previous post). This confirms the rule works
(both conditions are needed).


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

Exactly.


>> get from what ?

> In my example, both with "derived" and "middle".

ok.

>>
>> ...
>> [same code again]
>> ...
>>

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

Really, you may do it in two steps, because when you are
converting from "derived" to idevice, you are doing it within
the code of derived::as_idevice(), where the base class iodevice is
accessible, that is, you are in a contenxt where iodevice is a
public base class, which means the up-cast from "derived" to
idevice works.
Well, let me say that you make that conversion (in as_idevice())
without using a static_cast, but I remember that an implicit
conversion (that this return statement determines) invokes
the behaviour of the static_cast style in this case, which would
not work in case of private inaccessible base class.

Il giorno ven 29 nov 2024 alle ore 11:00 Federico Kircheis <
federico_at_[hidden]> ha scritto:

> 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 13:02:20