如果static_cast无效值枚举类会发生什么?


146

考虑以下C ++ 11代码:

enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);

假设data [0]实际上是100。根据标准设置的颜色是什么?特别是如果我以后再做

switch (color) {
    // ... red and yellow cases omitted
    default:
        // handle error
        break;
}

标准是否保证会违约?如果不是,在这里检查错误的正确,最有效,最优雅的方法是什么?

编辑:

作为奖励,标准是否对此做了任何保证,但带有简单的枚举?

Answers:


131

根据标准设置什么颜色?

引用C ++ 11和C ++ 14标准的报价:

[expr.static.cast] / 10

整数或枚举类型的值可以显式转换为枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。否则,结果值将不确定(并且可能不在该范围内)。

我们来看一下枚举值范围:[dcl.enum] / 7

对于基础类型固定的枚举,该枚举的值为基础类型的值。

在CWG 1766(C ++ 11,C ++ 14)之前 因此,对于data[0] == 100,将指定结果值(*),并且不涉及未定义行为(UB)。更一般而言,当您从基础类型转换为枚举类型时,in中的任何值data[0]都不会导致UB static_cast

在CWG 1766(C ++ 17)之后, 请参阅CWG缺陷1766。[expr.static.cast] p10段落得到了增强,因此如果将超出枚举可表示范围的值强制转换为枚举类型,则现在可以调用UB。这仍然不适用于问题中的场景,因为它data[0]是枚举的基础类型(请参见上文)。

请注意,CWG 1766被认为是标准中的缺陷,因此编译器实施者可以将其应用于其C ++ 11和C ++ 14编译模式。

(*)char至少必须为8位宽,但不必为unsigned。可存储的最大值至少127应符合C99标准的附录E。


比较[expr] / 4

如果在对表达式求值时,未在数学上定义结果或该类型的结果不在可表示值的范围内,则行为不确定。

在CWG 1766之前,转换积分类型->枚举类型会产生未指定的值。问题是:未指定的值是否可以超出其类型的可表示值?我相信答案是否定的 -如果答案是肯定的,那么您对有符号类型的操作所获得的保证在“此操作产生未指定的值”和“此操作具有未定义的行为”之间不会有任何区别。

因此,之前CWG 1766,甚至static_cast<Color>(10000)调用UB; 但是在CWG 1766之后,它调用UB。


现在,该switch语句:

[stmt.switch] / 2

条件应为整数类型,枚举类型或类类型。[...]进行整体促销。

[conv.prom] / 4

可以将其基础类型为固定(7.2)的无作用域枚举类型的prvalue转换为其基础类型的prvalue。此外,如果可以将积分提升应用于其基础类型,则其基础类型固定的无范围枚举类型的prvalue也可以转换为提升的基础类型的prvalue。

注意:不带enum-base的作用域枚举的基础类型是int。对于无作用域的枚举,其基础类型是实现定义的,但不得大于int如果int可以包含所有枚举器的值。

对于无范围的枚举,这使我们得出/ 1

以外的整数类型的prvalue boolchar16_tchar32_t,或wchar_t,其整数转换秩(4.13)小于的秩int可以被转换成类型的prvalue int如果int可以表示源类型的所有值; 否则,可以将源prvalue转换为type的prvalue unsigned int

在无范围枚举的情况下,我们将在int此处处理s。对于范围内的枚举(enum classenum struct),不应用整数提升。无论如何,积分提升也不会导致UB,因为存储的值在基础类型的范围内和的范围内int

[stmt.switch] / 5

switch执行该语句时,将评估其条件并将其与每种情况常量进行比较。如果大小写常量之一等于条件值,则将控制传递给匹配case标签后的语句。如果没有case常数与条件匹配,并且没有default标签,则控制权传递到由default标签标记的语句。

default标签应被击中。

注意:可以再看一下比较运算符,但是在引用的“比较”中未明确使用它。实际上,在我们的案例中,没有暗示会为有范围或无范围的枚举引入UB。


作为奖励,标准是否对此做了任何保证,但带有简单的枚举?

无论是否enum是作用不作任何这里的区别。但是,底层类型是否固定确实会有所不同。完整的[decl.enum] / 7是:

对于基础类型固定的枚举,该枚举的值为基础类型的值。否则,对于枚举其中Ë 分钟是最小的枚举和Ë 最大值是最大的,枚举的值是值的范围b 分钟b 最大值,定义如下:令K1对于二的补码表示和0用于一个人的补码或符号幅度表示。b max是大于或等于max(| e min | − K,| e max |)且等于2的最小值M -1,其中M是一个非负整数。如果 e min为非负值,则 b min为零,否则为 -(b max + K

让我们看一下以下枚举:

enum ColorUnfixed /* no fixed underlying type */
{
    red = 0x1,
    yellow = 0x2
}

请注意,我们无法将其定义为作用域枚举,因为所有作用域枚举都有固定的基础类型。

幸运的是,ColorUnfixed最小的枚举数是red = 0x1,因此max(| e min | − K,| e max |)等于| e max |。无论如何是yellow = 0x2。最小的值大于或等于2,等于2 中号 - 1为正整数M32 2 - 1)。(我认为目的是允许范围以1位为步长扩展。)因此,b max3bmin0

因此,100将在的范围之外ColorUnfixed,并且static_cast会在CWG 1766之前产生未指定的值,在CWG 1766之后会产生未定义的行为。


3
基础类型是固定的,因此枚举值的范围(第7.2节[dcl.enum] p7)是“基础类型的值”。100当然是的值char,因此“如果原始值在枚举值(7.2)的范围内,则该值不变。” 适用。
Casey

2
我不得不搜索以查找“ UB”的含义。(“未定义的行为”)这个问题没有提到未定义的行为的可能性。所以我没想到您可能在谈论这个。
卡拉多克

2
@karadoc我在该词首次出现时添加了一个链接。
dyp

1
喜欢这个答案。对于那些浏览过快的用户,请注意,只有在修改了代码以删除基础类型规范(在这种情况下为char)的情况下,最后一句话“因此,100将超出范围...”才适用。无论如何,我认为那是什么意思。
埃里克·塞帕宁

1
@Ruslan CWG 1766(或其分辨率)不是 C ++ 14的一部分,但我认为它将成为C ++ 17的一部分。即使使用C ++ 17规则,我也不完全理解“使答案的进一步文本无效”的意思。我的答案的其他部分主要涉及“枚举值的范围”是expr.static.cast p10所指的。
dyp '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.