将enum类变量重新解释为基础类型的引用是否安全?

时间:2013-10-20 11:28:04

标签: c++ c++11 reinterpret-cast enum-class

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

我知道对于基类的引用,如果是普通的类,则是安全的。但由于枚举类不是隐式转换为其底层类型的事件,因此我不确定上述代码是否以及如何保证在所有编译器中都能正常工作。有线索吗?

3 个答案:

答案 0 :(得分:13)

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

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重新解释cast [expr.reinterpret.cast]

     

1 表达式reinterpret_cast<T>(v)的结果是将表达式v转换为类型T的结果。如果T是左值引用类型或函数类型的右值引用,则结果为左值;如果T是对象类型的右值引用,则结果为xvalue;否则,结果是一个prvalue,并且对表达式v执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。 下面列出了可以使用reinterpret_cast明确执行的转化。使用reinterpret_cast无法明确执行其他转化。

(强调我的)

使用引用的重新解释基于5.2.10 / 11中的指针:

  

11 如果类型为“T1的指针”的表达式,则可以将类型T2的glvalue表达式强制转换为“T1”类型的表达式可以使用T2显式转换为“指向reinterpret_cast”的类型。结果引用与源glvalue相同的对象,但具有指定的类型。 [注意:也就是说,对于左值,参考广告reinterpret_cast<T&>(x)与内置*reinterpret_cast<T*>(&x)和{{1}的转化&具有相同的效果运算符(类似于*)。 - 结束注释]没有创建临时,没有复制,也没有调用构造函数(12.1)或转换函数(12.3)。

从而改变了这个问题:

reinterpret_cast<T&&>(x)

这是否合法:

reinterpret_cast<int8_t&>(foo)

下一站是5.2.10 / 7:

  

7 可以将对象指针显式转换为不同类型的对象指针。当“指向*reinterpret_cast<int8_t*>(&foo) 的指针”的prvalue v转换为“指向 cv T1的指针”时,结果为T2 {em> static_cast< cv T2*>(static_cast< cv如果void*>(v))T1都是标准的 - 布局类型(3.9)和T2的对齐要求不比T2更严格,或者两种类型都是T1。将“指向void”的类型的prvalue转换为“指向T1”的类型(其中T2T1是对象类型,{{T2的对齐要求1}}不比T2更严格,并且返回其原始类型会产生原始指针值。任何其他此类指针转换的结果都未指定。

鉴于3.9 / 9 T1和您的枚举类型都是标准布局类型,现在问题转变为:

int8_t

这是你运气不好的地方。 *static_cast<int8_t*>(static_cast<void*>(&foo)) 在5.2.9中定义,并且没有任何内容使上述合法 - 事实上5.2.9 / 5明确暗示它是非法的。其他条款没有帮助:

  • 5.2.9 / 13需要static_cast - &gt; T* - &gt; void*其中T*必须相同(省略 cv
  • 5.2.9 / 9和5.2.9 / 10不是关于指针,而是关于值
  • 5.2.9 / 11是关于类和类层次结构的
  • 5.2.9 / 12是关于类成员指针

我的结论是你的代码

T

不合法,其行为不是由标准定义的。

另请注意,上面提到的5.2.9 / 9和5.2.9 / 10负责使我在初始答案中给出的代码合法,并且您仍然可以在顶部找到。

答案 1 :(得分:1)

增量通过不同类型的左值访问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::i中存储值后访问intenum::e

但为什么不使用整数并根据需要进行转换?

for (int8_t i = static_cast<int8_t>(Foo::First);
     i <= static_cast<int8_t>(Foo::Last);
     ++i)
{
  Foo e = static_cast<Foo>(i);
  // ...
}

这是便携且安全的。

(恕我直言,这仍然不是一个好主意,因为可能有几个具有相同值的枚举数,或枚举类型的值没有相应的枚举器标签。)

答案 2 :(得分:0)

只要它转换为枚举的确切基础类型,它就是安全的。

如果枚举类的基础类型发生变化,++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);