C ++枚举类:强制转换为不存在的条目

时间:2019-03-28 07:20:21

标签: c++ enums undefined-behavior enum-class

我在一个项目中遇到这种情况,我们有一些套接字通信,主要交换字符以进行流控制。 我们在开关中将这些字符转换为--- ... aliases: [ hello, world, inserted ] --- ... 。 我想知道,如果另一端发送的字符不在我们的枚举类中,会发生什么情况。

我有这个mwe:

enum class : char

在此示例中,我有一个enum class Foo : char { UNKNOWN, ENUM1 = 'A', ENUM2 = 'B', ENUM3 = 'C' }; char bar1() { return 'B'; } char bar2() { return 'D'; } int main() { switch((Foo)bar1()) { case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break; case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break; case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break; case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break; default:std::cout << "DEFAULT" << std::endl;break; } switch((Foo)bar2()) { case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break; case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break; case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break; case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break; default:std::cout << "DEFAULT" << std::endl;break; } return 0; } ,其中包含一个未指定的条目和三个字符分配的条目。当我运行它时,我收到的输出是

enum class : char

这似乎可以正常工作,因为未定义的示例只是跳转到默认情况。但是,这是“省事”吗? 有一些我现在可能看不到的陷阱或其他并发症吗?

2 个答案:

答案 0 :(得分:2)

这是完全安全的,因为:

  • 您的enum class是一个范围枚举;
  • 您的枚举具有固定的基础类型: char;
  • 因此您的枚举值是类型char的值;
  • 因此将char值强制转换为枚举是完全有效的。

以下是与上述语句相对应的C ++ 17标准引号:

  

[dcl.enum] / 2:(...)枚举键enum classenum struct是   语义上等价的;用以下之一声明的枚举类型   这是一个作用域枚举,其枚举器的作用域   枚举器。

     

[dcl.enum] / 5:(...)每个枚举都有一个基础类型。的   可以使用枚举库显式指定基础类型。 (...)   在这两种情况下,底层类型都被认为是   固定。 (...)

     

[dcl.enum] / 8:对于基本类型固定的枚举,   枚举的值是基础类型的值。 (...)

     

[expr.static.cast] / 10 整数或枚举类型的值可以是   显式转换为完整的枚举类型。如果   枚举类型具有固定的基础类型,值是第一个   如有必要,可通过积分转换将其转换为该类型,然后   枚举类型。 [expr.cast] / 4由   const_cast,static_cast,static_cast后跟const_cast,   reinterpret_cast,reinterpret_cast和const_cast可以是   使用显式类型转换的强制转换符号执行。 (...)   如果可以通过多种方式解释转换   上面,使用了列表中第一个出现的解释(...)

如果基础类型不固定,则结论将有所不同。在这种情况下,[dcl.enum] / 8的其余部分将适用:它或多或少地表明,如果您不在枚举的最小和最大枚举器之内,则不确定该值是否可以代表。

另请参见问题Is it allowed for an enum to have an unlisted value?,该问题较为笼统(C ++和C),但不使用范围枚举或指定的基础类型。

这里是一个代码片段,用于使用未定义枚举器的枚举值:

switch((Foo)bar2()) {
    case Foo::UNKNOWN:          std::cout << "UNKNWON" << std::endl;break;
    case Foo::ENUM1:            std::cout << "ENUM1" << std::endl;break;
    case Foo::ENUM2:            std::cout << "ENUM2" << std::endl;break;
    case Foo::ENUM3:            std::cout << "ENUM3" << std::endl;break;
    case static_cast<Foo>('D'): std::cout << "ENUM-SPECIAL-D" << std::endl;break;
    default:                    std::cout << "DEFAULT" << std::endl;break;
}

答案 1 :(得分:1)

这并不完全安全。我发现C ++ Standard [expr.static.cast]第10段指出以下内容:

  

整数或枚举类型的值可以显式转换为枚举类型。如果原始值在枚举值(7.2)的范围内,则该值不变。否则,结果值将不确定(并且可能不在该范围内)。浮点类型的值也可以显式转换为枚举类型。结果值与将原始值转换为枚举的基础类型(4.9),然后转换为枚举类型相同。

7.2节说明了如何确定限制:

  

对于基础类型固定的枚举,该枚举的值是基础类型的值。否则,对于emin是最小的枚举数和emax是最大的枚举,该枚举的值是bmin到bmax范围内的值,定义如下:令K为2的补码表示形式,0为1的补码表示形式。补码或符号幅度表示。 bmax是大于或等于max(| emin |-K,| emax |)且等于2M -1的最小值,其中M是一个非负整数。如果emin为非负值,则bmin为零,否则为-(bmax + K)。如果bmin为零,则足以容纳枚举类型的所有值的最小位域的大小为max(M,1),否则为M +1。可以定义一个其枚举数未定义的枚举。如果枚举数列表为空,则枚举的值就像枚举有一个值为0的单个枚举数一样。

因此可能会在范围内将未定义的值强制转换为枚举,但是如果不在范围内,则它将为未定义。从理论上讲,可以定义一个具有较大值的枚举并确保强制转换可以正常工作,但是最好从枚举向整数类型进行反向转换并进行比较。