为什么C++ 11强类型枚举不能通过指针强制转换为底层类型?

Joh*_*nck 24 c++ enums c++11 enum-class strongly-typed-enum

在C++ 11中,我们可以将强类型的枚举(enum class)强制转换为其基础类型.但似乎我们不能指向相同的指针:

enum class MyEnum : int {};

int main()
{
  MyEnum me;

  int iv = static_cast<int>(me); // works
  int* ip = static_cast<int*>(&me); // "invalid static_cast"
}
Run Code Online (Sandbox Code Playgroud)

我试图理解为什么会这样:有什么关于枚举机制的东西让支持这个很难或没有意义吗?这是标准中的简单疏忽吗?别的什么?

在我看来,如果枚举类型真正构建在如上所述的整数类型之上,我们应该不仅能够投射值而且还能投射指针.我们仍然可以使用reinterpret_cast<int*>或者使用C风格的演员表,但这比我认为的更重要.

Fal*_*ias 13

TL; DR: C++的设计者不喜欢类型惩罚.

其他人指出为什么标准不允许这样做; 我将尝试解决为什么标准的编写者可能会这样做的原因.根据这个提议,强类型枚举的主要动机是类型安全.不幸的是,类型安全对许多人来说意味着很多东西 假设一致性是标准委员会的另一个目标是公平的,所以让我们在C++的其他相关背景下检查类型安全性.

C++类型安全

在C++中,除非明确指定相关(通过继承),否则类型是不相关的.考虑这个例子:

class A
{
    double x;
    int y;
};

class B
{
    double x;
    int y;
};

void foo(A* a)
{
    B* b = static_cast<B*>(a); //error
}
Run Code Online (Sandbox Code Playgroud)

即使A和B具有完全相同的表示(标准甚至称它们为"标准布局类型"),但如果没有a,则无法在它们之间进行转换reinterpret_cast.同样,这也是一个错误:

class C
{
public:
    int x;
};

void foo(C* c)
{
    int* intPtr = static_cast<int*>(c); //error
}
Run Code Online (Sandbox Code Playgroud)

即使我们知道C中唯一的东西是int,你可以自由访问它,但static_cast失败了.为什么?没有明确指出这些类型是相关的.C++旨在支持面向对象的编程,它提供了组合和继承之间的区别.您可以在通过继承关联的类型之间进行转换,但不能通过组合进行转换.

基于您所看到的行为,很明显强类型枚举通过组合与其基础类型相关联.为什么这可能是标准委员会选择的模型?

构成与继承

有很多关于这个问题的文章写得比我能适合的任何文章更好,但我会尝试总结一下.何时使用组合与何时使用继承肯定是一个灰色区域,但在这种情况下有许多点支持组合.

  1. 强类型枚举不能用作整数值.因此,由继承表示的'is-a'关系不适合.
  2. 在最高级别,枚举旨在表示一组离散值.通过为每个值分配id号来实现这一点的事实通常并不重要(不幸的是C暴露并因此强制执行这种关系).
  3. 回顾该提议,允许指定基础类型的列出原因是指定枚举的大小和签名.这更像是一个实现细节,而不是枚举的重要部分,再次支持组合.

在这种情况下,您可能会争论遗传或构成是否更好,但最终必须做出决定并且行为是以构图为模型的.

  • 你不能按位或强类型枚举(没有强制转换为整数类型)(见5.12/1).同意不幸的是,语法类似于继承,但现在我们无能为力. (2认同)

Mar*_*k B 8

相反,以稍微不同的方式看待它.你不能static_castlong*int*,即使intlong具有相同的底层表示.出于同样的原因,基于的枚举仍被int视为一种独特的,不相关的类型,int因此需要reinterpret_cast.

  • 不,foo不是`int`,它是用`int`实现的.这是两件截然不同的事情. (3认同)
  • 好吧,类的默认值是私有的而不是公共的,所以Foo"在-int-term-of-term"中是int. (2认同)

Com*_*sMS 7

枚举是具有命名常量的不同类型(3.9.2).[...]每个枚举定义一个与所有其他类型不同的类型.[...]如果两个枚举类型具有相同的基础类型,则它们是布局兼容的.

[dcl.enum](§7.2)

底层类型指定内存中枚举的布局,而不是它与类型系统中其他类型的关系(正如标准所说,它是一个独特的类型,它自己的类型).指向a的指针enum : int {}永远不会隐式转换为a int*,就像指向a的指针一样struct { int i; };,即使它们在内存中看起来都相同.

那么为什么隐式转换首先int起作用呢?

对于其基础类型是固定的枚举,枚举的值是基础类型的值.[...]通过整数提升(4.5)将枚举数或无范围枚举类型的对象的值转换为整数.

[dcl.enum](§7.2)

因此我们可以将枚举值赋给a,int因为它们属于类型int.int由于整数提升的规则,可以将枚举类型的对象分配给a .顺便说一下,这里的标准特别指出这只适用于C风格(无范围)枚举.这意味着你仍然需要static_cast<int>在你的例子的第一行中,但是一旦你enum class : int变成一个enum : int它就会在没有显式强制转换的情况下工作.尽管指针类型仍然没有运气.

整体促销在[conv.prom](§4.5)的标准中定义.我将为您提供引用完整部分的详细信息,但这里的重要细节是,其中的所有规则都适用于非指针类型的prvalues,因此这些都不适用于我们的小问题.

最后一部分可以在[expr.static.cast](§5.2.9)中找到,它描述了如何static_cast工作.

可以将范围枚举类型(7.2)的值显式转换为整数类型.

这也解释了为什么从你投enum classint的作品.

但请注意,static_cast指针类型允许的所有s(同样,我不会引用相当冗长的部分)需要类型之间的某种关系.如果你还记得答案的开头,那么每个枚举都是一个不同的类型,因此与它们的基础类型或同一基础类型的其他枚举没有任何关系.

这与@MarkB的答案相关:静态转换指向指针enum的指针int类似于将指针从一个整数类型转换为另一个整数类型 - 即使两者在下面都具有相同的内存布局,并且一个的值将隐式转换为另一个规则积分促销,它们仍然是无关的类型,所以static_cast在这里不起作用.


Ral*_*zky 6

我认为思考的错误就是这样

enum class MyEnum : int {};
Run Code Online (Sandbox Code Playgroud)

不是真正的继承.当然你可以说MyEnum 是一个 int.然而,从传统的继承不同,因为没有可用的所有操作ints为可MyEnum也.

让我们将其与以下内容进行比较:圆是椭圆.但是,实现a CirlceShape继承几乎总是错误的,EllipseShape因为并非椭圆上可能的所有操作都可以用于循环.一个简单的例子是在x方向上缩放形状.

因此,将枚举类视为从整数类型继承会导致您的情况混乱.您不能递增枚举类的实例,但可以增加整数.由于它不是真正的继承,因此禁止静态地将指针转换为这些类型是有意义的.以下行不安全:

++*reinterpret_cast<int*>(&me);
Run Code Online (Sandbox Code Playgroud)

这可能是委员会static_cast在这种情况下禁止的原因.一般reinterpret_cast被认为是邪恶的,而static_cast被认为是好的.