15

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?

4

3 回答 3

3

一个很好的观点。

注意C++03中5.2.7/3和5.2.7/4的写法如下

3如果v的类型与所需的结果类型相同(为方便起见,在本说明中将其称为R),或者它与R相同,只是R中的类对象类型比v中的类对象类型,结果为v(必要时进行转换)。

4如果在指针情况下v的值是空指针值,则结果是R类型的空指针值。

对 5.2.7/3 中引入的类型的引用R似乎暗示 5.2.7/4 旨在成为 5.2.7/3 的子条款。换句话说,似乎 5.2.7/4 仅适用于 5.2.7/3 中描述的条件,即类型相同时。

但是,C++11 中的措辞不同,不再涉及R,这不再暗示 5.2.7/3 和 5.2.7/4 之间有任何特殊关系。不知道是不是故意改的。。。

于 2012-07-02T21:46:01.220 回答
3

我相信该措辞的意图是一些转换可以在编译时完成,例如向上转换或dynamic_cast<Y*>((X*)0),但其他转换需要运行时检查(在这种情况下需要多态类型。)

如果您的代码片段格式正确,则需要运行时检查以查看它是否为空指针值,这与运行时检查只应针对多态情况发生的想法相矛盾。

请参阅DR 665,其中阐明了某些强制转换在编译时格式错误,而不是推迟到运行时。

于 2012-07-02T21:58:01.960 回答
1

对我来说,这似乎很明确。我认为当您错误地解释需求枚举是“else if .. else if ..”类型的事情时,就会出现混淆。

第 (1) 点和 (2) 点仅根据 cv-qualification 和 lvalue-rvalue-prvalue-- 等定义了允许的静态输入和输出类型。所以这很简单,适用于所有情况。

第 (3) 点非常清楚,如果输入和输出类型相同(除了添加 cv 限定符),那么转换是微不足道的(没有,或者只是添加了 cv 限定符)。

第(4)点明确要求如果输入指针为空,则输出指针也为空。这一点需要作为一个要求提出,而不是拒绝或接受强制转换(通过静态分析),而是强调一个事实,即如果从输入指针到输出指针的转换通常需要偏移到实际的指针值(因为它可以,在多继承类层次结构下),那么如果输入指针为空,则不得应用该偏移量,以保持指针的“空性”。这只是意味着在执行动态转换时,会检查指针是否为空,如果为空,则结果指针也必须具有空值。

第 (5) 点简单地指出,如果它是向上转型(从派生到基础),则转型是静态解决的(相当于static_cast<T>(v))。这主要是为了处理向上转换格式良好的情况(如脚注所示),但是如果要转到 v 指向的最衍生对象,则可能存在格式错误的转换(例如,如果 v 实际指向具有多个基类的派生对象,其中类 T 出现不止一次)。换句话说,这意味着,如果它是向上转换的,那么就静态地进行,没有运行时机制(因此,避免了不应该发生的潜在故障)。在这种情况下,编译器应该拒绝强制转换,就像它是static_cast<T>(v).

在第(6)点中,显然,“否则”直接指第(5)点(当然也指第(3)点的微不足道的情况)。含义(连同第(7)点),如果演员表不是向上转换(而不是身份转换(第(3)点)),那么它是向下转换,应该在运行时解决,明确要求(v的)类型是多态类型(具有虚函数)。

您的代码应该被符合标准的编译器拒绝。对我来说,毫无疑问。因为,强制转换是向下强制转换,并且 v 的类型不是多态的。它不符合标准规定的要求。空指针子句(第 (4) 点)实际上与它是否被接受的代码无关,它只与在强制转换中保留空指针值有关(否则,某些实现可能会使 (愚蠢) 选择仍然应用强制转换的指针偏移量,即使值为 null)。

当然,当基类型不是多态时,他们可以做出不同的选择,并允许转换表现为从基到派生的静态转换(即,没有运行时检查),但我认为这会中断动态转换的语义,这显然是在说“我想要对这个转换进行运行时检查”,否则你不会使用动态转换!

于 2012-07-02T23:29:21.963 回答