【问题标题】:What happens if you static_cast invalid value to enum class?如果你 static_cast 无效值到枚举类会发生什么?
【发布时间】:2013-08-12 19:38:16
【问题描述】:

考虑一下这个 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;
}

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

【问题讨论】:

    标签: c++ language-lawyer


    【解决方案1】:

    根据标准设置什么颜色?

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

    [expr.static.cast]/10

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

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

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

    在 CWG 1766(C++11、C++14)之前 因此,对于data[0] == 100,结果值是指定的(*),不涉及Undefined Behaviour (UB)。更一般地说,当您从基础类型转换为枚举类型时,data[0] 中的任何值都不会导致 static_cast 的 UB。

    CWG 1766 (C++17) 之后CWG defect 1766。 [expr.static.cast]p10 段落已得到加强,因此如果您将枚举的可表示范围之外的值转换为枚举类型,您现在可以调用 UB。这仍然不适用于问题中的场景,因为data[0] 是枚举的底层类型(见上文)。

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

    (*) char 要求至少为 8 位宽,但不要求为 unsigned。根据 C99 标准的附录 E,可存储的最大值至少为 127


    与 [expr]/4 比较

    如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。

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

    因此,在 CWG 1766 之前,即使 static_cast&lt;Color&gt;(10000)不会调用 UB;但在 CWG 1766 之后,它确实调用了 UB。


    现在,switch 声明:

    [stmt.switch]/2

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

    [conv.prom]/4

    unscoped 枚举类型(其基础类型是固定的(7.2))的纯右值可以转换为其基础类型的纯右值。此外,如果可以将整型提升应用于其基础类型,则其基础类型固定的无作用域枚举类型的纯右值也可以转换为提升的基础类型的纯右值。

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

    对于无范围的枚举,这将我们引向 /1

    boolchar16_tchar32_twchar_t 以外的整数类型的纯右值,其整数转换等级 (4.13) 小于 int 的等级可以转换为type int if int 可以表示源类型的所有值;否则,源纯右值可以转换为unsigned int 类型的纯右值。

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

    [stmt.switch]/5

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

    default 标签应该被点击。

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


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

    enum 是否在此范围内没有任何区别。但是,底层类型是否固定确实会有所不同。完整的 [decl.enum]/7 是:

    对于基础类型固定的枚举,枚举的值是基础类型的值。否则,对于 emin 是最小枚举数且 emax 是最大枚举数的枚举,枚举是 bminbmax 范围内的值,定义如下:设K 1 表示二进制补码表示,0 表示一个补码或符号幅度表示。 bmax是大于等于max(|emin|的最小值-K,|emax|) 并且等于 2M − 1,其中M 是一个非负整数。 bmin 如果 emin 是非负数且 -(bmax + K) 否则。

    我们来看看下面的枚举:

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

    请注意,我们不能将其定义为范围枚举,因为所有范围枚举都有固定的基础类型。

    还好ColorUnfixed的最小枚举数是red = 0x1,所以max(|emin| - K, |emax| ) 在任何情况下都等于 |emax|,即yellow = 0x2。大于或等于2 的最小值,对于正整数M 等于2M - 13 (22 - 1)。 (我认为目的是允许范围以 1 位为单位扩展。)因此 bmax3bmin em> 是0

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

    【讨论】:

    • 底层类型是固定的,所以枚举值的范围(§7.2 [dcl.enum] p7)是“底层类型的值”。 100肯定是char的值,所以“如果原始值在枚举值(7.2)的范围内,值不变”。适用。
    • 我不得不搜索一下“UB”是什么意思。 ('未定义的行为')问题没有提到未定义行为的可能性;所以我没想到你可能在谈论这个。
    • @karadoc 我在第一次出现该术语时添加了一个链接。
    • 喜欢这个答案。对于那些浏览得太快的人,请注意最后一句“因此,100 将超出范围......”仅适用于修改代码以删除基础类型规范(本例中为 char)的情况。无论如何,我认为这就是它的意思。
    • @Ruslan CWG 1766(或其决议)不是 C++14 的一部分,但我认为它将成为C++17 的一部分。即使使用 C++17 规则,我也不太明白“使您的答案的进一步文本无效”是什么意思。我的答案的其他部分主要关注“枚举值的范围”是 expr.static.cast p10 所指的。
    猜你喜欢
    • 2017-01-29
    • 2017-01-29
    • 2017-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-08
    相关资源
    最近更新 更多