1

To use dynamic_casting, the inheritance hierarchy needs to be polymorphic. But in the below code segment, dynamic_cast works perfectly for upcasting while it fails at compile time for downcasting? What's the reason upcasting works?

class B{};
class C : public B{};

int main()
{
    C* pcob = new C();

    B* bp = dynamic_cast<B*>(pcob);     // #1 : Upcasting works

    if(bp)
        cout << "casted" << endl;
    else
        cout << "bp null" << endl;
    delete bp;    

    B* pbob = new B();

    C* pc = dynamic_cast<C*>(pbob);     // #2 : Downcasting gives error

    if(pc)
        cout << "casted" << endl;
    else
        cout << "pc null" << endl;

    delete pbob;

    return 0;
}

The compile time error at #2 is

main.cpp: In function ‘int main()’:
main.cpp:36:34: error: cannot dynamic_cast ‘pbob’ (of type ‘class B*’) to type ‘class C*’ (source type is not polymorphic)
 C* pc = dynamic_cast<C*>(pbob);
deepdive
  • 9,720
  • 3
  • 30
  • 38

4 Answers4

3

class B needs at least one virtual method. Otherwise the type is not polymorphic and dynamic_cast can not be applied for downcasting. The former case is implicit type conversion.

But dynamic_cast is used explicitly. How compiler can ignore it?

From standard (5.2.7/5)

If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type cv2 D such that B is a base class of D, the result is the unique B subobject of the D object referred to by v. 68 The result is an lvalue if T is an lvalue reference, or an xvalue if T is an rvalue reference. In both the pointer and reference cases, the program is ill-formed if cv2 has greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D. [ Example:

struct B { };
struct D : B { };
void foo(D* dp) {
  B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp;
}

—end example ]

Rakib
  • 7,435
  • 7
  • 29
  • 45
  • But that's not true for #1 scenario – deepdive Jun 19 '14 at 04:34
  • @deepdive, in case #1, the conversion is implicit. – Rakib Jun 19 '14 at 04:35
  • @RakibulHasan I know there must be virtual destructor, but to make the error happen at compile time in #2 scenario, I omitted knowingly! – deepdive Jun 19 '14 at 04:41
  • Ahhh you beat me to it. – Captain Obvlious Jun 19 '14 at 04:49
  • @CaptainObvlious, you are getting old :D – Rakib Jun 19 '14 at 04:50
  • There's always more to the story ;) – Captain Obvlious Jun 19 '14 at 05:01
  • "Otherwise polymorphic use will lead to resource leak and hard to find bugs." - not necessarily, the *only* problematic action is destruction/deletion via a base class reference/pointer. "...only base destructor will be called, so resource allocated in derived class and deleted in derived destructor will be actually deleted." - pretty sure you mean "will ***not*** be", but it's technically wrong anyway - it's undefined behaviour - you can't assume that missing deletions of members accurately describes the behavioural consequences. – Tony Delroy Jun 19 '14 at 05:48
  • @TonyD, thanks for commenting. That part was not necessary to answer the question, so I removed. but I dont get why it will be UB to use base pointer/reference to delete derived object? could u plz post some details? – Rakib Jun 19 '14 at 06:00
  • 1
    There's a Stackoverflow question about it and an answer quoting the Standard [here](http://stackoverflow.com/questions/2100644/will-using-delete-with-a-base-class-pointer-cause-a-memory-leak) - hope that helps. Cheers. – Tony Delroy Jun 19 '14 at 06:12
1
  • #1 works - C has all the capabilities of B with some other ones
  • #2 fails - B does not have all the capabilities of a C

Set theory really

Ed Heal
  • 59,252
  • 17
  • 87
  • 127
1

dynamic_cast does not require a polymorphic type for upcasting.

Some basics from 5.2.7/1 [expr.dynamic.cast]

The result of the expression dynamic_cast(v) is the result of converting the expression v to type T.

However this does not cover why the type does not have to be polymorphic. To get a slightly better understanding the following quotes from 5.2.7/5 and 5.2.7/6 clear it up a bit.

If T is "pointer to cv1 B" and v has type "pointer to cv2 D" such that B is a base class of D, the result is a pointer to the unique B sub-object of the D object pointed to by v.

struct B { };
struct D : B { };
void foo(D* dp) {
    B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp;
}

The above provides the specific instance of upcasting from D to B with compatible cv qualifiers (const-volatile).

The next part 5.2.7/6 sheds a little more light on why the source type does not have to be polymorphic

Otherwise, v shall be a pointer to or an lvalue of a polymorphic type.

the key here is the use of "Otherwise". To look at this from the other direction If D is not implicitly convertible to B then D must be polymorphic otherwise a polymorphic type is not required.

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
1

The Standard's been quoted - praise be.

To get an idea why the Standard specifies the behaviour it does, it helps to consider what you're asking the compiler to do and how that relates to common implementations of runtime type information....

Your dynamic_cast<B*>(pcob) only needs to know the offset of a B relative to the containing C - that's a constant amount for every C object, even if that C is embedded in some other object. There's no reliance on anything unknown until runtime.

Contrast that with dynamic_cast<C*>(pbob) for B* pbob - the compiler can't generally know whether the pointed-to B is embedded in another object at all, let alone whether that object happens to be - or also be derived from - C, such that the cast can succeed. In general, it needs to rely on the runtime type information only generated for polymorphic types.

In your specific code, the compiler knows the actual types of all the objects, and with only a little effort could have supported the downcast, but similar code in a function that gets a B* parameter or calls some opaque factory method returning a B* may sometimes dynamic_cast with a runtime object containing a C and other times not - the compiler can't employ any "localised" knowledge of the real types. In local usage, you can easily create pointers to the actual runtime type, so there's no utility gained from requiring the compilers to support "localised" usage, and the Standard simply bans such casts wholesale.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252