c++ 将枚举类变量reinterpret_cast为基础类型的引用安全吗?

aemubtdh  于 2023-01-14  发布在  其他
关注(0)|答案(3)|浏览(147)

我看到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))
{
    ...
}

我知道在普通类的情况下,强制转换到基类的引用是安全的。但是由于枚举类不是事件隐式转换到它们的底层类型,我不确定上面的代码是否以及如何保证在所有编译器中工作。有线索吗?

zfciruhq

zfciruhq1#

如果你真的想迭代枚举的值,你可能需要为你的枚举重载运算符++

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值;否则,结果为右值,并对表达式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的指针"的纯右值v转换为类型"指向 * cv * T2的指针"时,如果T1T2都是标准布局类型,则结果为static_cast< * cv * T2*>(static_cast< * cv * void*>(v))(3.9),且T2的对齐要求不严格于T1。或者如果任一类型是void,则将类型为"指向T1的指针"的右值转换为类型"指向T2的指针"(其中T1T2是对象类型,并且T2的对齐要求不比T1的对齐要求更严格),然后返回到其原始类型,将生成原始指针值。任何其他此类指针转换的结果都是未指定的。
给定Python 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负责使代码合法,我在最初的答案中给出了代码,您仍然可以在顶部找到代码。

jdgnovmf

jdgnovmf2#

增量通过不同类型的左值访问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);
  // ...
}

这是便携式和安全的。
(It IMHO仍然不是一个好主意,因为可能有几个枚举器具有相同的值,或者枚举类型的值没有对应的枚举器标签。)

xv8emn3q

xv8emn3q3#

只要强制转换为枚举的确切基础类型,它就是安全的。
如果枚举类的基础类型发生更改,则++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);

相关问题