如果枚举类型无法适应无符号整数类型,会发生什么?

45

根据Bathsheba的要求并作为对“如果枚举类型无法适应整数类型会发生什么?”问题的跟进:

假设枚举定义如下:

enum foo : unsigned int
{
    bar = UINT_MAX,
    oops
};

oops的值是否已定义?


MSVS2015编译:

warning C4340: 'oops': value wrapped from positive to negative value
warning C4309: 'initializing': truncation of constant value
warning C4369: 'oops':  enumerator value '4294967296' cannot be represented as 'unsigned int', value is '0'

MSVS2015输出:

bar = 4294967295
oops= 0

gcc 4.9.2编译:

9 : note: in expansion of macro 'UINT_MAX'
bar = UINT_MAX,
^
10 : error: enumerator value 4294967296l is outside the range of underlying type 'unsigned int'
oops
^
Compilation failed

gcc 4.9.2 的输出

//compilation failed

1
似乎“int”会是一个更有趣的问题,因为这样“bar + 1”的计算会导致未定义行为。 - Kerrek SB
4
嗯。UINT_MAX + 1显然是一个可以在编译时求值的常量表达式,但考虑到它不能容纳在“unsigned”中,C++标准希望编译器选择long long作为枚举类型,但由于你强制它成为“unsigned”,所以它无法这样做。对我来说,这解释了编译器错误,甚至可能是答案。标准中的那行“如果底层类型是固定的,则可以将值转换为其提升后的底层类型”的语句也与此有关。 - Bathsheba
4
@Bathsheba 我刚刚测试了一下,如果你不限制类型,你只会收到一个警告:http://coliru.stacked-crooked.com/a/a8306b64279b700c - NathanOliver
1
@NathanOliver 在使用gcc(6.2.1)时,甚至不会收到警告。 - Shmuel H.
2
[dcl.enum]/2 和 7 在这里似乎很相关。"如果枚举器的初始化值不能由基础类型表示,则程序是非法的。" 这很有趣,但我不确定它是否适用。目前正在尝试理解标准术语。 - Revolver_Ocelot
显示剩余8条评论
1个回答

20

这是一个非常有趣的问题。简单的答案是这个实际上是未定义的:标准并没有针对这种情况做出任何规定。

为了举一个更好的例子,考虑这个enum

 enum foo : bool { True=true, undefined };
根据标准:
[dcl.enum]/2: [...] 如果一个枚举值没有初始化器,则该枚举值的值为前一个枚举值的值加一。
因此,在我们的例子中,foo::undefined的值为2(true + 1),无法表示为bool类型。

是否非法?

不是,根据标准,只有“非固定底层类型”存在不能表示所有枚举值的限制:
[dcl.enum]/7: 对于其基础类型未固定的枚举,如果没有整数类型可以表示所有枚举值,则该枚举是非法的。
对于不可表示所有枚举值的“固定底层类型”,标准并未做出任何规定。

原始问题中oops和undefined的值是多少?

它是未定义的:标准没有针对这种情况的任何规定。
foo::undefined的可能值为:
- 最大可能值(true):undefined和oops应该是底层类型的最大值。 - 最小可能值(false):底层类型的最小值。注意:在有符号整数中,它将不匹配整数溢出的当前行为(未定义行为)。 - 随机值(?):编译器将选择一个值。
所有这些值的问题在于,可能会导致两个字段具有相同的值(例如,foo :: True == foo :: undefined)。

“初始化器”(例如undefined=2)和“隐式”初始化器(例如True=true,undefined)之间的区别

根据标准:
[dcl.enum]/5:如果底层类型已固定,则在右括号之前的每个枚举器的类型为底层类型,并且“enumerator-definition”中的“constant-expression”必须是底层类型的转换常量表达式。
换句话说:
 enum bar : bool { undefined=2 };

等价于

 enum bar : bool { undefined=static_cast<bool>(2) };

然后bar::undefined将变为true。在"implicit"初始化程序中,情况并非如此:这个标准段落仅适用于初始化程序,而不是"implicit"初始化程序。

摘要

  1. 通过这种方式,具有fixed-underlying-typeenum可以具有无法表示的值。
  2. 它们的值在标准中未定义。

根据问题和评论,这在GCC和clang中无效,但在MSVS-2015中有效(带有警告)。


1
我喜欢你的回答,但如果基础类型是无符号的,它是否真的未定义呢?你引用了 [dcl.enum]/7,这应该(因为溢出是为无符号类型设计的)导致超出范围的值首先变为0... - Simon Kraemer
1
我也不确定在这里使用 bool 是否是最佳选择,因为底层数据既不带符号也不无符号,并且它也不会溢出(据我所知),这是一个相当特殊的情况。 - Simon Kraemer
1
@SimonKraemer 我也曾经和你想法一样,但你应该记住,枚举值的类型与其底层类型不同。换句话说,标准应该指出枚举值应该转换为其*底层类型(请参见“初始化程序(例如undefined=2)和“隐式”初始化程序(例如True=true,undefined)之间的区别”部分)。 - Shmuel H.
感谢您的澄清。 - Simon Kraemer
1
@SimonKraemer 这只是一个有趣的观点:在非固定底层类型枚举的情况下,使用普通的unsigned int会被认为是不合法的 - 标准在[dcl.enum]/7中已经明确指出。 - Shmuel H.

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