Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a standard-compliant compiler reject code containing dynamic_cast downcast from non-polymorphic type?

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]):

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

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

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

  4. If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type T.

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

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

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

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

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

like image 463
Ben Voigt Avatar asked Jul 02 '12 21:07

Ben Voigt


1 Answers

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

like image 56
AnT Avatar answered Sep 17 '22 12:09

AnT