165

考虑这个 C++ 代码:

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;
}

标准是否保证违约会受到打击?如果不是,那么在这里检查错误的正确、最有效、最优雅的方法是什么?标准是否对此做出任何保证,但使用普通枚举?

4

1 回答 1

144

根据标准设置的颜色是什么?

引用 C++11 和 C++14 标准的引用来回答:

[expr.static.cast]/10

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

让我们查看枚举值的范围:[dcl.enum]/7

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

在 CWG 1766 (C++11, C++14) 之前 因此,对于data[0] == 100,结果值被指定(*),并且不涉及未定义行为(UB) 。更一般地,当您从基础类型转换为枚举类型时,没有任何值data[0]可以导致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)的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。此外,如果可以将整数提升应用于其基础类型,则其基础类型固定的无范围枚举类型的纯右值也可以转换为提升的基础类型的纯右值。

注意:没有enum-base的作用域枚举的基础类型是int. 对于无范围枚举,底层类型是实现定义的,但不应大于int如果int可以包含所有枚举数的值。

对于无范围的枚举,这导致我们 /1

如果可以表示源类型的所有值,则可以将除bool, char16_t,之外的整数类型的纯右值char32_t,或者wchar_t其整数转换等级(4.13)小于等级的int纯右值转换为类型的纯右值;否则,源纯右值可以转换为类型的纯右值。intintunsigned int

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

[stmt.switch]/5

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

default标签应该被击中。

注意:可以再看一下比较运算符,但它没有在所提到的“比较”中明确使用。事实上,在我们的例子中,没有任何迹象表明它会为作用域或非作用域枚举引入 UB。


作为奖励,标准是否对此做出任何保证,但使用普通枚举?

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

对于基础类型固定的枚举,枚举的值是基础类型的值。否则,对于其中e min是最小枚举数并且e max是最大枚举数的枚举,枚举的值是b minb max范围内的值,定义如下: 让Kbe1对于二进制补码表示和0对于 a一个的补码或符号幅度表示。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 M - 1M3( 2 2 - 1 )。(我认为目的是允许范围以 1 位为单位扩展。)因此b max is3bmin is 0

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

于 2013-08-12T19:43:38.057 回答