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);
>}
> 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