我正在研究C ++标准n4713.pdf。考虑下面的代码:
#include <iostream>
#include <type_traits>
enum UEn
{
EN_0,
EN_1,
EN_L = 0x7FFFFFFFFFFFFFFF // EN_L has type "long int"
}; // UEn has underlying type "unsigned long int"
int main()
{
long lng = 0x7FFFFFFFFFFFFFFF;
std::cout << std::boolalpha;
std::cout << "typeof(unsigned long == UEn):" << std::is_same<unsigned long, std::underlying_type<UEn>::type>::value << std::endl; // Outputs "true"
std::cout << "sizeof(EN_L):" << sizeof(EN_L) << std::endl;
std::cout << "sizeof(unsigned):" << sizeof(unsigned) << std::endl;
std::cout << "sizeof(unsigned long):" << sizeof(unsigned long) << std::endl;
std::cout << "sizeof(unsigned long):" << sizeof(unsigned long long) << std::endl;
lng = EN_L + 1; // Invokes UB as EN_L is 0x7FFFFFFFFFFFFFFF and has type "long int"
return 0;
}
上面的代码输出(在g ++-8.1,Clang上测试):
typeof(unsigned long == UEn):true
sizeof(EN_L):8
sizeof(unsigned):4
sizeof(unsigned long):8
sizeof(unsigned long):8
根据10.2p5节(10.2枚举声明):
在枚举说明符的右括号之后,每个枚举器都有 枚举的类型...如果基础类型不固定,则 闭括号前的每个枚举器的类型确定为 如下:
如果为枚举器指定了初始化程序,则 constant-expression应该是整数常量表达式(8.6)。如果 表达式具有无范围的枚举类型,枚举器具有 该枚举类型的基础类型,否则具有相同的值 类型作为表达式。
如果没有为第一个指定初始化程序 枚举数,其类型是未指定的带符号整数类型。
否则,枚举器的类型与 前面的枚举数,除非增量值无法表示 在那种类型的情况下,这种类型是未指定的整数类型 足以包含增加的值。如果不存在这样的类型, 该程序格式不正确。
此外,第10.2p7节规定:
对于基础类型不固定的枚举,基础 type是一个整数类型,可以表示所有枚举器值 在枚举中定义。如果没有整数类型可以代表所有 枚举值,则枚举格式不正确。它是 实现定义哪种整数类型用作基础 类型,但基础类型不得大于int 除非枚举数的值不能适合int或unsigned
因此,我有以下问题:
UEn
是类型unsigned long
的整数常量,因此0x7FFFFFFFFFFFFFFF
的类型也是{时,为什么枚举long int
的基础类型是EN_L
{1}}。这是编译器错误还是行为明确?long int
时,是否不应该暗示枚举数和枚举的整数类型也应该匹配?这两个彼此不同的原因可能是什么?答案 0 :(得分:2)
基础类型是实现定义的。它仅必须能够表示每个枚举数,并且除非需要,否则不能大于int
。正如您已经发现的那样,对于每个dcl.enum.7,没有对签名的要求(除了基本类型必须能够代表每个枚举数)。这限制了枚举器类型的反向传播,超出了您的假设。值得注意的是,它并没有说枚举的基本类型必须是任何枚举器的初始化程序的类型。
Clang首选无符号整数作为枚举基,而不是有符号整数;这里的所有都是它的。重要的是,枚举的类型不必匹配任何特定的枚举器的类型:它仅必须能够表示每个枚举器。这在其他情况下是相当正常且容易理解的。例如,如果您有EN_1 = 1
,即使1是int
,枚举的基类型也不是unsigned int
或int
也不会令您感到惊讶。 / p>
您也可以正确地说0x7fffffffffffffff
的类型为long
。 Clang同意您的看法,但是implicitly casts the constant to unsigned long
:
TranslationUnitDecl
`-EnumDecl <line:1:1, line:5:1> line:1:6 Foo
|-EnumConstantDecl <line:2:5> col:5 Frob 'Foo'
|-EnumConstantDecl <line:3:5> col:5 Bar 'Foo'
`-EnumConstantDecl <line:4:5, col:11> col:5 Baz 'Foo'
`-ImplicitCastExpr <col:11> 'unsigned long' <IntegralCast>
`-IntegerLiteral <col:11> 'long' 576460752303423487
这是允许的,因为正如我们之前所说,枚举的基本类型不需要是任何枚举的逐字记录类型。
当标准规定每个枚举器都有枚举的类型时,这意味着EN_1
的类型为枚举的右括号之后的enum UEn
。请注意“在右括号之后”和“在右括号之前”提到。
在右括号之前,如果枚举没有固定类型,则每个枚举器的类型都是其初始化表达式的类型,但这只是临时的。例如,这就是使您即使在EN_2 = EN_1 + 1
范围内也无需强制转换EN_1
即可编写enum class
的方法。在右括号之后,情况不再如此。您可以通过检查错误消息或查看反汇编来诱使编译器向您显示:
template<typename T>
T tell_me(const T&& value);
enum Foo {
Baz = 0x7ffffffffffffff,
Frob = tell_me(Baz)
// non-constexpr function 'tell_me<long>' cannot be used in a constant expression
};
请注意,在这种情况下,T
被推断为long
,但是在右括号之后,它被推断为Foo
:
template<typename T>
T tell_me(const T&& value);
enum Foo {
Baz = 0x7ffffffffffffff
};
int main() {
tell_me(Baz);
// call Foo tell_me<Foo>(Foo const&&)
}
如果您希望用Clang对枚举类型进行签名,则需要使用: base_type
语法进行指定,或者需要使用负枚举器。
答案 1 :(得分:2)
我认为,这种(绝对不直观的)警告的答案在于7.6整体促销[conv.prom]:
未范围枚举类型的prvalue,其基础类型不是 固定(10.2)可以转换为以下第一个的prvalue 以下类型可以代表枚举的所有值 (即10.2中所述的b min 到b max 范围内的值):
int
,unsigned int
,long int
,unsigned long int
,long long int
或unsigned long long int
。
也就是说,如果您的基础类型不是固定的,并且您在表达式中使用了枚举成员,则不一定将其转换为枚举的基础类型。相反,它将转换为该列表中所有成员都适合的第一种类型。
别问我为什么,这条规则对我来说似乎很疯狂。
本节继续说:
基础类型为的无范围枚举类型的prvalue 固定(10.2)可以转换为其基础类型的prvalue。
即如果您使用unsigned long
修复基础类型:
enum UEn : unsigned long
...
然后警告消失了。
摆脱警告(并使基础类型不固定)的另一种方法是添加需要存储unsigned long
的成员:
EN_2 = 0x8000000000000000
然后,警告消失。
好问题。我从回答中学到了很多。
答案 2 :(得分:0)
第10.2p5节的措词明确指出“ ...在右括号之前”建议以下解释。在枚举类型定义中的枚举类型(在大括号之前)被选择为某种足以表示其值的整数类型。然后,可以在同一枚举的后续枚举数定义中重用此值。当遇到枚举类型的关闭括号时,编译器将选择一个足够大的整数类型来表示所有枚举器值。定义枚举类型后,所有枚举器值都具有相同的类型(即枚举类型),并共享枚举的基础类型。例如:
#include <iostream>
#include <typeinfo>
#include <type_traits>
enum E1
{
e1 = 0, // type of the initializer (int), value = 0
e2 = e1 + 1U, // type of the initializer (unsigned = int + unsigned), value = 1U
e3 = e1 - 1, // type of the initializer (int = int - int), value = -1
}; // range of values [-1, 1], underlying type is int
int main()
{
std::cout << typeid(std::underlying_type<E1>::type).name() << '\n';
std::cout << typeid(e1).name() << '\n';
std::cout << typeid(e2).name() << '\n';
std::cout << typeid(e3).name() << '\n';
}
使用clan5和gcc8运行,并输出:
i
2E1
2E1
2E1