Can a standard-compliant compiler reject code containing dynamic_cast downcast from non-polymorphic type?
c++, dynamic-cast, language-lawyer, polymorphism, virtual
Solution
A very good point.
Note that in C++03 the wording of 5.2.7/3 and 5.2.7/4 is as follows
3 If the type of v is the same as the required result type (which, for convenience, will be called R in this description), or it is the same as R except that the class object type in R is more cv-qualified than the class object type in v, the result is v (converted if necessary).
4 If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type R.
The reference to type `R` introduced in 5.2.7/3 seems to imply that 5.2.7/4 is intended to be a sub-clause of 5.2.7/3. In other words, it appears that 5.2.7/4 is intended to apply only under the conditions described in 5.2.7/3, i.e. when types are the same.
However, the wording in C++11 is different and no longer involves `R`, which no longer suggests any special relationship between 5.2.7/3 and 5.2.7/4. I wonder whether it was changed intentionally...
Problem
This question is inspired by comments here. Consider the following code snippet: ``` struct X {}; // no virtual members struct Y : X {}; // may or may not have virtual members, doesn't matter Y* func(X* x) { return dynamic_cast<Y*>(x); } ``` Several people suggested that their compiler would reject the body of `func`. However, it appears to me that whether this is defined by the Standard depends on the run-time value of `x`. From section 5.2.7 (`[expr.dynamic.cast]`): The result of the expression `dynamic_cast<T>(v)` is the result of converting the expression `v` to type `T`. `T` shall be a pointer or reference to a complete class type, or "pointer to cv `void`." The `dynamic_cast` operator shall not cast away constness. If `T` is a pointer type, `v` shall be a prvalue of a pointer to complete class type, and the result is a prvalue of type `T`. If `T` is an lvalue reference type, `v` shall be an lvalue of a complete class type, and the result is an lvalue of the type referred to by `T`. If `T` is an rvalue reference type, `v` shall be an expression having a complete class type, and the result is an xvalue of the type referred to by `T`. If the type of `v` is the same as `T`, or it is the same as `T` except that the class object type in `T` is more cv-qualified than the class object type in `v`, the result is `v` (converted if necessary). If the value of `v` is a null pointer value in the pointer case, the result is the null pointer value of type `T`. 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`. 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`. Otherwise, `v` shall be a pointer to or an lvalue of a polymorphic type. If `T` is "pointer to cv `void`," then the result is a pointer to the most derived object pointed to by `v`. Otherwise, a run-time check is applied to see if the object pointed or referred to by `v` can be converted to the type pointed or referred to by `T`.) The most derived object pointed or referred to by `v` can contain other `B` objects as base classes, but these are ignored. If `C` is the class type to which `T` points or refers, the run-time 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 run-time check fails. The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws `std::bad_cast`. The way I read this, the requirement of a polymorphic type only applies if none of the above conditions are met, and one of those conditions depends on the runtime value. Of course, in a few cases the compiler can positively determine that the input cannot properly be NULL (for example, when it is the `this` pointer), but I still think the compiler cannot reject the code unless it can determine that the statement will be reached (normally a run-time question). A warning diagnostic is of course valuable here, but is it Standard-compliant for the compiler to reject this code with an error?