C++ 枚举 class:转换为不存在的条目
C++ enum class: Cast to non existing entry
我在一个项目中遇到过这种情况,我们有一些套接字通信,主要交换字符以进行流量控制。
我们在开关中将这些字符转换为 enum class : char
。
我想知道,如果另一端发送的字符不在我们的枚举 class.
中,可能会发生什么
我有这个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
这似乎完美无缺,因为未定义的示例只是跳转到默认情况。然而,这是"save to do"吗?
是否有一些我现在可能看不到的陷阱或其他并发症?
这并不完全安全。我发现 C++ 标准 [expr.static.cast] 第 10 段规定如下:
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range). A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration (4.9), and subsequently to the enumeration type.
7.2 部分解释了如何确定限制:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a one’s complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
所以它 可能 将未定义的值转换为枚举,如果它在范围内,但如果不在范围内,则它是未定义的。从理论上讲,可以定义一个具有大值的枚举,并确保转换工作正常,但最好从枚举向后转换为整数类型,然后进行比较。
这是完全安全的,因为:
- 您的
enum class
是范围枚举;
- 您的枚举具有固定的基础类型
: char
;
- 因此您的枚举值是
char
类型的值;
- 所以将 char 值转换为枚举是完全有效的。
此处对应上述语句的C++17标准引用:
[dcl.enum]/2: (...) The enum-keys enum class
and enum struct
are
semantically equivalent; an enumeration type declared with one of
these is a scoped enumeration, and its enumerators are scoped
enumerators.
[dcl.enum]/5: (...) Each enumeration also has an underlying type. The
underlying type can be explicitly specified using an enum-base. (...)
In both of these cases, the underlying type is said to be
fixed. (...)
[dcl.enum]/8: For an enumeration whose underlying type is fixed,
the values of the enumeration are the values of the underlying type. (...)
[expr.static.cast]/10 A value of integral or enumeration type can be
explicitly converted to a complete enumeration type. If the
enumeration type has a fixed underlying type, the value is first
converted to that type by integral conversion, if necessary, and then
to the enumeration type. [expr.cast]/4 The conversions performed by
a const_cast, a static_cast, a static_cast followed by a const_cast, a
reinterpret_cast, a reinterpret_cast followed by a const_cast, can be
performed using the cast notation of explicit type conversion. (...)
If a conversion can be interpreted in more than one of the ways listed
above, the interpretation that appears first in the list is used (...)
如果基础类型不固定,结论就会不同。在这种情况下,[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;
}
我在一个项目中遇到过这种情况,我们有一些套接字通信,主要交换字符以进行流量控制。
我们在开关中将这些字符转换为 enum class : char
。
我想知道,如果另一端发送的字符不在我们的枚举 class.
我有这个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
这似乎完美无缺,因为未定义的示例只是跳转到默认情况。然而,这是"save to do"吗? 是否有一些我现在可能看不到的陷阱或其他并发症?
这并不完全安全。我发现 C++ 标准 [expr.static.cast] 第 10 段规定如下:
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range). A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration (4.9), and subsequently to the enumeration type.
7.2 部分解释了如何确定限制:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a one’s complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
所以它 可能 将未定义的值转换为枚举,如果它在范围内,但如果不在范围内,则它是未定义的。从理论上讲,可以定义一个具有大值的枚举,并确保转换工作正常,但最好从枚举向后转换为整数类型,然后进行比较。
这是完全安全的,因为:
- 您的
enum class
是范围枚举; - 您的枚举具有固定的基础类型
: char
; - 因此您的枚举值是
char
类型的值; - 所以将 char 值转换为枚举是完全有效的。
此处对应上述语句的C++17标准引用:
[dcl.enum]/2: (...) The enum-keys
enum class
andenum struct
are semantically equivalent; an enumeration type declared with one of these is a scoped enumeration, and its enumerators are scoped enumerators.[dcl.enum]/5: (...) Each enumeration also has an underlying type. The underlying type can be explicitly specified using an enum-base. (...) In both of these cases, the underlying type is said to be fixed. (...)
[dcl.enum]/8: For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. (...)
[expr.static.cast]/10 A value of integral or enumeration type can be explicitly converted to a complete enumeration type. If the enumeration type has a fixed underlying type, the value is first converted to that type by integral conversion, if necessary, and then to the enumeration type. [expr.cast]/4 The conversions performed by a const_cast, a static_cast, a static_cast followed by a const_cast, a reinterpret_cast, a reinterpret_cast followed by a const_cast, can be performed using the cast notation of explicit type conversion. (...) If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used (...)
如果基础类型不固定,结论就会不同。在这种情况下,[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;
}