将枚举类变量reinterpret_cast到基础类型的引用是否安全

Is it safe to reinterpret_cast an enum class variable to a reference of the underlying type?

本文关键字:类型 引用 安全 是否 类变量 枚举 reinterpret cast      更新时间:2023-10-16

我已经看到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的指针"的 prvalue v转换为类型"指向 cv T2 的指针"时,如果T1T2都是标准布局类型 (3.9),并且T2的对齐要求不比 T1 更严格,则结果static_cast< cv T2*>(static_cast< cv void*>(v)), 或者,如果任一类型为 void 。将类型为"指向T1的指针"的 prvalue 转换为"指向T2的指针"类型(其中T1T2是对象类型,其中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);