枚举类型的值初始化行为

37
首先,根据cppreference.com的说法,枚举类型的值初始化是不可能的。
根据http://en.cppreference.com/w/cpp/language/value_initialization,枚举类型的值初始化实际上执行的是零初始化。因此,根据http://en.cppreference.com/w/cpp/language/zero_initialization,零初始化枚举类型的效果是:
如果T是标量类型,则对象的初始值是整型常数零隐式转换为T。
然而,整型常数零不能隐式转换为枚举类型。最终,枚举类型无法进行值初始化。这听起来很奇怪,但在VC、GCC和clang上,枚举类型的值初始化确实可以工作。那么,标准对此有什么规定呢?
其次,根据http://en.cppreference.com/w/cpp/language/static_cast:
整型、浮点型或枚举类型可以转换为任何完整的枚举类型(如果表达式的值转换为枚举类型的基础类型后不是目标枚举值之一,则结果是未指定(C++17之前),自C++17起是未定义行为)。这是否意味着,如果目标枚举没有一个等于0的枚举量,那么对枚举进行值初始化(如果可能的话)实际上可能会导致未定义的行为?

4
回复:第二点。这不是标准所说的。来自C++14:“5.2.9/10 可以将整数或枚举类型的值显式地转换为枚举类型。如果原始值在枚举值的范围内(7.2),则该值保持不变。否则,结果值是未指定的(可能不在该范围内)。 " 强调是我的。枚举值的范围被定义(由7.2/8)如此一来,0总是落在其中。 - Igor Tandetnik
@IgorTandetnik cppreference 已更新。 - Cubbi
2
零初始化的描述也是错误的。[dcl.init]/6.1表示,“如果T是标量类型(3.9),则对象将被初始化为将整数字面值0(零)转换为T所得到的值”。请注意缺少“隐式”的字眼。@Cubbi对于static_cast的修复仍然是错误的;当前的描述仅适用于底层类型不固定的枚举型。根据[dcl.enum]/8,“对于底层类型固定的枚举型,枚举的值为其底层类型的值。” - T.C.
@T.C.编辑,谢谢!如果你有任何需要更正的地方,请不要犹豫,这是公开可编辑的。 - Cubbi
2个回答

4

1:这可以这样理解:

enum class SomeEnum : int { V1 = 0, V2 = 1, V3 = 2 }; 
SomeEnum a = 0; // compile error
SomeEnum b = SomeEnum.V1; // OK

这是对未定义行为的基本保护!

2:是的和是的 :)

SomeEnum c = static_cast<SomeEnum>(1); // = SomeEnum.V2
SomeEnum d = static_cast<SomeEnum>(5); // undefined behavior

static_cast 被定义为危险的,它只应用于支持序列化或旧的 C 接口!


1
有趣的是,这段代码竟然在编译时没有错误,并且完全忽略了正确性,但从测试结果看“表现良好”:http://coliru.stacked-crooked.com/a/21a0f0256e412848(使用GCC和Clang测试通过)。 - Damon
3
static_cast<SomeEnum>(5); 不是未定义行为。5 在枚举可表示的值范围内;您甚至明确说明了底层类型为 int。参见:如果将无效值 static_cast 到枚举类会发生什么? - dyp
从技术上讲,static_cast<SomeEnum>(5); 是有效的,但从语言角度来看,它并不是有效的,因为没有相应的值。因此,从语言角度来看,这种行为是未定义的,但在实践中是可预测的。 - Mux
3
不,语言确切地指定了 static_cast<SomeEnum>(5) 的行为。你或者任何程序员都可能不希望枚举类型的变量与该枚举类型的所有枚举器不相等,但是语言并不提供这样的保证。一种使用没有对应枚举器的枚举值的方法是将其转换为底层类型,即 static_cast<int>(static_cast<SomeEnum>(x)) == x 对于所有可以表示为 int 的值 x 都成立(语言保证),假设 intSomeEnum 的底层类型。 - dyp

4
此问题在评论中已得到回答。下面是我尝试解释背后整个标准的内容:
对于类型T的对象或引用进行“零初始化”的含义如下:
- 如果T是标量类型(3.9),则将对象初始化为将整数字面值0(零)转换为T所得到的值;
(枚举是标量类型;§3.9/9)
因此,由于未说明转换是隐式的,因此我们不会查看§4,而是§5.2.9;
表达式static_cast(v)的结果是将表达式v转换为类型T的结果。
然后§5.2.9 / 10定义了如何将整数值转换为枚举类型。
整数型或枚举型的值可以显式转换为枚举型。如果原始值在枚举值范围内,则该值不变(7.2)。否则,结果值是未指定的(可能不在该范围内)。
必须表明对于所有枚举值,零都在枚举值范围内。
下面五个引文摘自§7.2/8:
对于具有固定底层类型的枚举,枚举的值是底层类型的值。
由于所有允许的底层类型都包括零在其值范围内*,这将自动给出所需的结果。现在,对于没有固定底层类型的枚举,
否则,对于枚举,其中e_min是最小的枚举器,e_max是最大的枚举器,枚举的值是范围b_min至b_max中的值,如下所示:
即我们必须表明b_min始终小于或等于零,而b_max始终大于或等于零。
令K为具有二进制补码表示法的1,具有一进制或符号幅度表示法的0。
b_max是大于或等于max(| e_min |-K,| e_max |)的最小值,并等于2M-1,其中M是非负整数。 |emax|是非负的,而两个数的最大值至少与这两个数本身一样大。因此,max(|emin| − K, |emax|)也是非负的,bmax必须大于或等于该数——这样我们就满足了第一个要求。

如果emin为非负值,则b min为零;否则为−(bmax+K)

bmin显然为零或者负数:bmax是非负的,如上所述,K是非负的(为0或1),因此它们的和的加法逆元是非正的。我们满足了第二个要求。最后,

如果enumerator-list为空,则枚举的值就好像枚举具有单个值为0的枚举一样。

通过将emin = emax = 0,可以得到如上结果。
  • 这可以归结为断言“所有整型都在其取值范围内有零”,留给读者证明。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接