将枚举类变量reinterpret_cast到基础类型的引用是否安全
Is it safe to reinterpret_cast an enum class variable to a reference of the underlying type?
我已经看到reinterpret_cast
用于将增量应用于枚举类,我想知道这种用法在标准C++中是否可以接受。
enum class Foo : int8_t
{
Bar1,
Bar2,
Bar3,
Bar4,
First = Bar1,
Last = Bar4
};
for (Foo foo = Foo::First; foo <= Foo::Last; ++reinterpret_cast<int8_t &>(foo))
{
...
}
我知道在平凡类的情况下,强制转换为基类的引用是安全的。但是由于枚举类不是事件隐式转换为其底层类型的,因此我不确定上面的代码是否以及如何保证在所有编译器中工作。有什么线索吗?
如果您真的想迭代枚举的值,则可能需要重载枚举的运算符++
:
Foo& operator++( Foo& f )
{
using UT = std::underlying_type< Foo >::type;
f = static_cast< Foo >( static_cast< UT >( f ) + 1 );
return f;
}
和使用
for (Foo foo = Foo::First; foo <= Foo::Last; ++foo)
{
...
}
要回答是否允许reinterpret_cast
的问题,这一切都从 5.2.10/1 开始:
5.2.10 重新诠释演员表
1 表达式
reinterpret_cast<T>(v)
的结果是将表达式v
转换为类型T
的结果。如果T
是左值引用类型或对函数类型的右值引用,则结果为左值;如果T
是对对象类型的右值引用,则结果为 x值;否则,结果是一个 prvalue,并且对表达式v
执行左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 的标准转换。下面列出了可以使用reinterpret_cast
显式执行的转换。不能使用reinterpret_cast
显式执行其他转换。
(强调我的)
使用引用的重新解释基于5.2.10/11中的指针:
11 如果"指向
T1
的指针"类型的表达式可以使用reinterpret_cast
显式转换为"指向T2
的指针"类型,则可以将类型T1
的glvalue表达式转换为"对T2
的引用"类型。结果引用与源 glvalue 相同的对象,但具有指定的类型。[ 注意:也就是说,对于左值,引用强制转换reinterpret_cast<T&>(x)
与具有内置&
和*
运算符的转换*reinterpret_cast<T*>(&x)
具有相同的效果(对于reinterpret_cast<T&&>(x)
也是如此)。 不创建临时函数,不创建副本,也不调用构造函数 (12.1) 或转换函数 (12.3)。
这就把问题从这里转化了:
reinterpret_cast<int8_t&>(foo)
这是否合法:
*reinterpret_cast<int8_t*>(&foo)
下一站是 5.2.10/7:
7 对象指针可以显式转换为不同类型的对象指针。当类型为"指向
T1
的指针"的 prvaluev
转换为类型"指向 cvT2
的指针"时,如果T1
和T2
都是标准布局类型 (3.9),并且T2
的对齐要求不比T1
更严格,则结果static_cast<
cv
T2*>(static_cast<
cv
void*>(v))
, 或者,如果任一类型为void
。将类型为"指向T1
的指针"的 prvalue 转换为"指向T2
的指针"类型(其中T1
和T2
是对象类型,其中T2
的对齐要求并不比T1
的对齐要求严格)并返回其原始类型将生成原始指针值。未指定任何其他此类指针转换的结果。
给定 3.9/9 int8_t
和枚举类型都是标准布局类型,问题现在转换为:
*static_cast<int8_t*>(static_cast<void*>(&foo))
这就是你不走运的地方。 static_cast
在 5.2.9 中定义,没有任何内容使上述内容合法 - 事实上 5.2.9/5 清楚地暗示它是非法的。其他子句无济于事:
- 5.2.9/13 要求
T*
->void*
->T*
其中T
必须相同(省略 cv)
5.2.9/9 - 和 5.2.9/10 不是关于指针,而是关于值
- 5.2.9/11 是关于类和类层次结构的
- 5.2.9/12 是关于类成员指针的
我由此得出的结论是,您的代码
reinterpret_cast<int8_t&>(foo)
是不合法的,其行为不是由标准定义的。
另请注意,上面提到的 5.2.9/9和 5.2.9/10 负责使我在初始答案中给出的代码合法,您仍然可以在顶部找到它。
通过不同类型的左值访问foo
的值,这是未定义的行为,除非在 3.10 [basic.lval] 中列出的情况。枚举类型及其基础类型不在该列表中,因此代码具有未定义的行为。
对于一些支持非标准扩展的编译器,您可以通过类型双关语来实现:
union intenum
{
int8_t i;
Foo e;
};
intenum ie;
for (ie.e = Foo::First; ie.e <= Foo::Last; ++ie.i)
// ...
但这也不是可移植的,因为标准不允许在intenum::e
中存储值后访问intenum::i
。
但是为什么不只使用整数并根据需要进行转换呢?
for (int8_t i = static_cast<int8_t>(Foo::First);
i <= static_cast<int8_t>(Foo::Last);
++i)
{
Foo e = static_cast<Foo>(i);
// ...
}
这是便携式和安全的。
(恕我直言,这仍然不是一个好主意,因为可能有多个具有相同值的枚举器,或者枚举类型的值没有相应的枚举器标签。
只要它强制转换为枚举的确切底层类型,它就是安全的。
如果枚举类的基础类型发生更改,则该++reinterpret_cast<int8_t &>(foo)
以静默方式中断。
更安全的版本:
foo = static_cast<Foo>(static_cast<std::underlying_type<Foo>::type>(foo) + 1);
或
++reinterpret_cast<std::underlying_type<Foo>::type&>(foo);
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 将成员函数的返回类型引用到C++中的自定义类
- 非类型引用参数可以在运行时修改,这是否意味着模板可以在运行时实例化?
- C++:类类型引用运算符=用法
- 将 const 类型引用对象注册为类成员对象C++
- 错误:绑定到类型引用会丢弃限定符
- 如果作为基类型引用传递,派生类型会解构吗
- 未分配返回未定义对象类型引用的 C++ 函数的返回值时会发生什么情况
- 类型引用的初始化无效
- C++ 从函数返回多个类型引用并将它们强制转换为我们需要的类型
- 函数常量返回类型:类型引用的初始化无效
- 将类型的嵌套类型引用为类模板的参数的问题
- 与返回类型(引用、常量引用)混淆
- 调用具有const键类型引用的std::指针集的count方法
- 返回运算符类型=-引用或值
- 非类型引用形参/实参
- c++中定义不同类型引用的区别
- 为什么返回类型引用输出流
- 非类型(引用)模板参数和链接
- 类型引用的初始化无效