This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 110b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2023-01-15


665. Problems in the specification of dynamic_cast

Section: 7.6.1.7  [expr.dynamic.cast]     Status: CD2     Submitter: Daniel Krügler     Date: 1 December 2007

[Voted into the WP at the March, 2009 meeting.]

At least one implementation accepts the following example as well-formed (returning a null pointer at runtime), although others reject it at compile time:

    struct A { virtual ~A(); };
    struct B: private A { } b;
    A* pa = dynamic_cast<A*>(&b);

Presumably the intent of 7.6.1.7 [expr.dynamic.cast] paragraph 5 is that all up-casts (converting from derived to base) are to be handled at compile time, regardless of whether the class involved is polymorphic or not:

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... In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D.

One explanation for the implementation that accepts the example at compile time is that the final sentence is interpreted as part of the condition for the applicability of this paragraph, so that this case falls through into the description of runtime checking that follows. This (mis-)interpretation is buttressed by the example in paragraph 9, which reads in significant part:

    class A { virtual void f(); };
    class B { virtual void g(); };
    class D : public virtual A, private B {};
    void g() {
        D d;
        B* bp;
        bp = dynamic_cast<B*>(&d); // fails
    }

The “fails” comment is identical to the commentary on the lines in the example where the run-time check fails. If the interpretation that paragraph 5 is supposed to apply to all up-casts, presumably this comment should change to “ill-formed,” or the line should be removed from the example altogether.

It should be noted that this interpretation (that the example is ill-formed and the runtime check applies only to down-casts and cross-casts) rejects some programs that could plausibly be accepted and actually work at runtime. For example,

    struct B { virtual ~B(); };
    struct D: private virtual B { };

    void test(D* pd) {
        B* pb = dynamic_cast<B*>(pd); // #1
    }

    struct D2: virtual B, virtual D {};

    void demo() {
        D2 d2;
        B* pb = dynamic_cast<B*>(&d2); // #2
        test(&d2); // #3
    }

According to the interpretation that paragraph 5 applies, line #1 is ill-formed. However, converting from D2 to B (line #2) is well-formed; if the alternate interpretation were applied, the conversion in line #1 could succeed when applied to d2 (line #3).

One final note: the wording in 7.6.1.7 [expr.dynamic.cast] paragraph 8 is incorrect:

The run-time check logically executes as follows:

All uses of T in this paragraph treat it as if it were a class type; in fact, T is the type to which the expression is being cast and thus is either a pointer type or a reference type, not a class type.

Proposed resolution (June, 2008):

  1. Change 7.6.1.7 [expr.dynamic.cast] paragraph 5 as follows:

  2. ...In both the pointer and reference cases, cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class of D the program is ill-formed if cv2 is greater cv-qualification than cv1 or if B is an inaccessible or ambiguous base class of D.
  3. Change the comment in the example in 7.6.1.7 [expr.dynamic.cast] paragraph 9 as follows:

  4.     bp = dynamic_cast<B*>(&d);     // fails ill-formed (not a run-time check)
    
  5. Change 7.6.1.7 [expr.dynamic.cast] paragraph 8 as follows:

  6. The If C is the class type to which T points or refers, the run-time check logically executes as follows: