Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use dynamic_cast to downcast correctly?

I am being very confused about dynamic_cast. Material from C++ Primer and cppreference(rule 5) can't help me understand. (cppreference is way much harder than the book and I read them both very carefully)

From C++ Primer 5th:
dynamic_cast<type*>(e)

In all cases, the type of e must be either a class type that is publicly derived from the target type, a public base class of the target type, or the same as the target type. If e has one of these types, then the cast will succeed...

So here's how I understand the quoted text above:

(Base class have virtual functions)

dynamic_cast succeeds if :

  1. e is a public inherited derived class from type. e is children. Upcast.
  2. e is a base class of type? type is children. Downcast.
  3. e is same as type. Sidecast?

Sample code:

#include <iostream>
using namespace std;

struct A {
    virtual void foo() {}
};

struct B : A {

};

struct C : B {

};

int main()
{
    A* pa = new B;
    if (C* pc = dynamic_cast<C*>(pa)) {
        cout << "1";    //B is a base class of C
    }
    return 0;
}

I don't understand why this downcast would fail, I think it satisfies condition 2. and rule 5) (from cppreference).


If the book is wrong(damn once again), would someone elaborate rule 5) from cppreference? I can't fully understand what it say without examples...

like image 800
Rick Avatar asked Sep 28 '18 13:09

Rick


2 Answers

Here is the rule from cppreference with my annotations:

5) If expression is a pointer or reference to a polymorphic type Base, and new_type is a pointer or reference to the type Derived a run-time check is performed:

This applies. B is a base of C.

a) The most derived object pointed/identified by expression is examined. If, in that object, expression points/refers to a public base of Derived, and if only one subobject of Derived type is derived from the subobject pointed/identified by expression, then the result of the cast points/refers to that Derived subobject. (This is known as a "downcast".)

The most dervied object pointed by pa is of type B.

Although B is a public base of C, the particular instance which pa points to, is not an instance of B base subobject of a C instance. The pointed B instance is a "concrete" object. So, this case does not apply.

An example:

C  c;
B* bp = &c; // bp points to base subobject of C
C* cp = dynamic_cast<C*>(bp);
assert(cp);

B  b2;
B* bp2 = &b2; // bp does not point to a base subobject
C* cp2 = dynamic_cast<C*>(bp2);
assert(!cp2);

b) Otherwise, if expression points/refers to a public base of the most derived object, and, simultaneously, the most derived object has an unambiguous public base class of type Derived, the result of the cast points/refers to that Derived (This is known as a "sidecast".)

pa does not point to a most derived object whose base class is C, so this case does not apply.

An example of side cast:

struct base {
    virtual ~base(){}; // for polymorphism
};
struct left : base {};
struct right : base {};
struct derived : left, right {};

derived d;
left* l = &d;
right* r = dynamic_cast<right*>(l);

c) Otherwise, the runtime check fails. If the dynamic_cast is used on pointers, the null pointer value of type new_type is returned. If it was used on references, the exception std::bad_cast is thrown.

Neither 5a nor 5b cases apply, so this "otherwise" case 5c does.


  1. e is same as type. Sidecast?

Not a sidecast. A sidecast is explained in 5b. Casting to same type is just an identity cast (rarely useful, so not a commonly used terminology either).


It may be that the conditions of the book attempt describe whether the conversion is well-formed. Although, "then the cast will succeed" certainly seems to imply more. The quoted rules are not correct for describing whether the cast succeeds at runtime.

If the entire program is well-formed, then a compiler must compile the program. If an expression is ill-formed, then a compiler must give you a diagnostic message saying that you did wrong.

The example program that you've shown is well-formed and it must successfully compile. It does compile on my system.

like image 130
eerorika Avatar answered Sep 30 '22 18:09

eerorika


The last part is that the dynamic type of the object must match.

Here, you have a B pointed to by an A pointer. You are trying to dynamically cast the pointer to get a pointer to C, but there is no C to point to. So the cast fails.

dynamic cast doesn't create objects, it just lets you access objects that are already there. When you call new B it creates a B object with an A subobject. It does not create a C object.

like image 44
Yakk - Adam Nevraumont Avatar answered Sep 30 '22 19:09

Yakk - Adam Nevraumont