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

C++ enum class: Cast to non existing entry

本文关键字:不存在 转换 枚举 C++      更新时间:2023-10-16

我在一个项目中遇到这种情况,我们有一些套接字通信,主要交换字符进行流控制。 我们将这些角色投射到开关中的enum class : char。 我想知道,如果另一端发送一个不在我们的枚举类中的字符,会发生什么。

我有这个mwe:

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,其中包含一个未指定的条目和三个字符分配的条目。当我运行它时,我收到的输出是

ENUM2
DEFAULT

这似乎完美无缺,因为未定义的示例只是跳转到默认情况。但是,这是"保存要做"吗? 是否有一些我现在可能看不到的陷阱或其他并发症?

这是完全安全的,因为:

  • 您的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 的其余部分将适用:它或多或少地说,如果您不在枚举的最小和最大枚举器中,则不确定是否可以表示该值。

另请参阅问题是否允许枚举具有未列出的值?,这是更通用的(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;
}

它并不完全安全。我发现C++标准[expr.static.cast]第10段陈述如下:

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

7.2 部分解释了如何确定限制:

对于基础类型为固定的枚举,枚举的值是基础类型的值。否则,对于 emin 是最小枚举器而 emax 是最大枚举器的枚举,枚举的值是 bmin 到 bmax 范围内的值,定义如下:设 K 为 1 表示二进制补码表示,0 表示 1 的补码或符号量级表示。bmax 是大于或等于 max(|emin| − K, |emax|) 且等于 2M − 1 的最小值,其中 M 是非负整数。如果 emin 为非负数,则 bmin 为零,否则为 −(bmax + K)。如果 bmin 为零,则大到足以容纳枚举类型的所有值的最小位字段的大小为 max(M, 1),否则为 M + 1。可以定义具有任何枚举器未定义的值的枚举。如果枚举器列表为空,则枚举的值就像枚举具有值为 0 的单个枚举器一样。

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

相关文章: