17

我曾经见过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))
{
    ...
}

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

4

3 回答 3

19

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

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

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

(强调我的)

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

11如果“pointer to”类型T1的表达式可以使用. 结果引用与源 glvalue 相同的对象,但具有指定的类型。[注意:也就是说,对于左值,引用强制转换与使用内置和运算符的转换具有相同的效果(对于 也是类似的)。—尾注] 不创建临时文件,不复制副本,也不调用构造函数 (12.1) 或转换函数 (12.3)。T2T1T2reinterpret_castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)&*reinterpret_cast<T&&>(x)

这改变了这个问题:

reinterpret_cast<int8_t&>(foo)

这是否合法:

*reinterpret_cast<int8_t*>(&foo)

下一站是 5.2.10/7:

7对象指针可以显式转换为不同类型的对象指针。当v“pointer to T1”类型的纯右值转换为“pointer to cv T2 ”类型时,结果是如果和都是标准布局类型(3.9)并且 的对齐要求不比 的更严格,或者如果任一类型是。将“pointer to”类型的纯右值转换为“pointer to ”类型(其中和是对象类型,其中 的对齐要求不比 的 更严格)并返回其原始类型会产生原始指针值。未指定任何其他此类指针转换的结果。static_cast<cv T2*>(static_cast<cv void*>(v))T1T2T2T1voidT1T2T1T2T2T1

鉴于 3.9/9int8_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*whereT必须相同(省略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 负责使我在最初的答案中给出的代码合法,您仍然可以在顶部找到这些代码。

于 2013-10-20T12:05:56.653 回答
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);
  // ...
}

这是便携且安全的。

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

于 2013-11-08T21:23:18.497 回答
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);
于 2013-10-20T12:01:58.147 回答