繁体   English   中英

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

[英]Why can't C++11 strongly-typed enum be cast to underlying type via pointer?

在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"
}

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

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

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
}

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

class C
{
public:
    int x;
};

void foo(C* c)
{
    int* intPtr = static_cast<int*>(c); //error
}

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

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

构成与继承

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

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

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

相反,以稍微不同的方式看待它。 即使intlong具有相同的底层表示,也不能static_cast long* to int* 对于相同的同样的理由基于枚举int被尚未处理为一个唯一的,不相关的类型来int ,因此需要reinterpret_cast

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

[dcl.enum] (§7.2)

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

那么为什么隐式转换为int工作呢?

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

[dcl.enum] (§7.2)

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

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

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

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

这就解释了为什么从enum classint

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

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

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

enum class MyEnum : int {};

不是真正的继承。 当然你可以说MyEnum 是一个 int 但是,它与经典继承不同,因为并非所有可用于int的操作都可用于MyEnum

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

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

++*reinterpret_cast<int*>(&me);

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

您可以在标准草案中的5.2.9静态铸造部分找到您的问题的答案。

支持允许

int iv = static_cast<int>(me);

可以从以下获得:

5.2.9 / 9范围枚举类型(7.2)的值可以显式转换为整数类型。 如果原始值可以由指定的类型表示,则该值不变。 否则,未指定结果值。

支持允许

me = static_cast<MyEnum>(100);

可以从以下获得:

5.2.9 / 10可以将整数或枚举类型的值显式转换为枚举类型。 如果原始值在枚举值(7.2)的范围内,则该值不变。 否则,结果值未指定(可能不在该范围内)。

支持不允许

int* ip = static_cast<int*>(&me);

可以从以下获得:

5.2.9 / 11类型为“指向cv1 B的指针”的prvalue,其中B是类类型,可以转换为类型为“指向cv2 D的指针”的prvalue,其中D是从B派生的类(第10条) ,如果存在从“指向D的指针”到“指向B的指针”的有效标准转换(4.10),则cv2与cv1具有相同的cv资格,或者更高的cv资格,并且B既不是虚拟基类D也不是D的虚基类的基类。空指针值(4.10)被转换为目标类型的空指针值。 如果“指向cv1 B的指针”类型的prvalue指向实际上是D类型对象的子对象的B,则生成的指针指向类型D的封闭对象。否则,转换的结果是未定义的。

static_cast不能用于将&me转换为int*因为MyEnumint与继承无关。

我认为第一个static_cast的原因是能够使用期望旧样式enum函数和库,或者甚至使用一堆定义的值进行枚举,并直接期望一个整数类型。 但是类型enum和整数类型之间没有其他逻辑关系,所以如果你想要那个转换你应该使用reinterpret_cast 但如果你有reinterpret_cast问题,你可以使用自己的助手:

template< class EnumT >
typename std::enable_if<
    std::is_enum<EnumT>::value,
    typename std::underlying_type<EnumT>::type*
>::type enum_as_pointer(EnumT& e)
{
    return reinterpret_cast<typename std::underlying_type<EnumT>::type*>(&e);
}

要么

template< class IntT, class EnumT >
IntT* static_enum_cast(EnumT* e, 
    typename std::enable_if<
        std::is_enum<EnumT>::value &&
        std::is_convertible<
            typename std::underlying_type<EnumT>::type*,
            IntT*
        >::value
    >::type** = nullptr)
{
    return reinterpret_cast<typename std::underlying_type<EnumT>::type*>(&e);
}

虽然这个答案可能无法满足您关于reason of prohibiting static_cast of enum pointersreason of prohibiting static_cast of enum pointers ,但它为您提供了一种使用reinterpret_cast的安全方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM