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