Rick
Rick

Reputation: 7506

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

Upvotes: 8

Views: 4830

Answers (5)

EwingKang
EwingKang

Reputation: 1

I would like to add my two cents on top of @eerorika 's fabulous answer, primarily focusing on the phrase of

If only one object of Derived type is derived from the subobject pointed/identified by an expression, then....

I think this is to prevent downcasting given the following condition:

           Base  
             |
          Derived
          /      \
         |        V 
         |      Right
          \      /
           V    V
         MostDerived

In this case, there 2 objects of Derived in MostDerived. Upon casting a Base pointer, which points to a MostDerived instance, to a Derived pointer, it is impossible for the compiler to know which of the Derived object one's referring to. (The one that is referred by MostDerived or the one that's referred by Right.)

In fact, this is the exact case of ambiguous base access and will cause compiler warning or failed compilation depends on your implementation.

Upvotes: 0

Arne Vogel
Arne Vogel

Reputation: 6676

I'm, for the most part, adding another example of a side cast to previous answers…

struct A {};
struct B { virtual ~B() = default; };
struct C : A, B {};

A *side_cast()
{
    B *obj = new C;
    return dynamic_cast<A *>(obj);
}

The above is a legal "side cast", and does not return null. This shows that the target type neither:

  • has to be polymorphic.
  • has to be related to the static type of *expression.

for the cast to succeed at run time.

The cast is well-formed, regardless of whether the type of the most-derived object pointed to by expression (IOW, the dynamic type of *expression) inherits from the target type. However, it will return static_cast<A *>(nullptr) unless A is a public and unambiguous base class of the dynamic type of *expression. The gist of this is that you can legally write a whole bunch of nonsensical casts like dynamic_cast<std::tuple<int, float> *>(&std::cin) – note that std::cin is of type std::istream which is polymorphic –, but you'll simply get a null pointer at runtime.

Simply put, a dynamic_cast between pointers can do most of the things that a static_cast can (except, at least, non-polymorphic downcast*), and, when the static type of *expression is polymorphic, it can also cast to any pointer to class whatsoever, except for removing cv-qualifers (const or volatile). However, whether the cast actually returns non-null depends on the specific condition, checked at run time, mentioned above.

* The reason why this is forbidden is that there is no safe way to do this, and dynamic_cast is expected to be safe. Hence they make you write static_cast to make it clear that any ensuing UB is your fault.

Upvotes: 0

eerorika
eerorika

Reputation: 238351

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.

Upvotes: 4

Bathsheba
Bathsheba

Reputation: 234715

The issue is that the statement A* pa = new B; creates a B.

But a B doesn't contain a C (downwards, upwards, or sideways), so the dynamic cast from pa to a C* will certainly fail.

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275395

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.

Upvotes: 4

Related Questions