27

在 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 风格的铸件,但这比我认为我们需要的要大。

4

6 回答 6

14

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. 回顾一下提案,列出的允许指定底层类型的原因是指定枚举的大小和签名。这更像是一个实现细节,而不是枚举的重要部分,再次有利于组合。

在这种情况下,您可能会争论好几天是继承还是组合更好,但最终必须做出决定,并且行为是以组合为模型的。

于 2014-07-08T21:01:35.033 回答
8

相反,以稍微不同的方式看待它。即使具有相同的基础表示,您也不能static_cast这样long*做。出于同样的原因,基于的枚举仍被视为唯一的、不相关的类型,因此需要.int*intlongintintreinterpret_cast

于 2014-07-04T04:33:03.593 回答
8

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

[dcl.enum] (§7.2)

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

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

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

[dcl.enum] (§7.2)

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

整体促销在[conv.prom] (§4.5) 的标准中定义。我将省略引用完整部分的细节,但这里重要的细节是那里的所有规则都适用于非指针类型的纯右值,所以这些都不适用于我们的小问题

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

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

这就解释了为什么你的演员从enum class工作int

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

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

于 2014-07-08T14:21:23.500 回答
6

我认为思考的错误在于

enum class MyEnum : int {};

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

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

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

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

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

于 2014-07-04T09:25:11.830 回答
3

您的问题的答案可以在标准草案的第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 的指针”类型的纯右值,其中 B 是类类型,可以转换为“指向 cv2 D 的指针”类型的纯右值,其中 D 是从 B 派生的类(第 10 条) ,如果存在从“指向 D 的指针”到“指向 B 的指针”的有效标准转换 (4.10),则 cv2 与 cv1 具有相同的 cv 限定或大于 cv1 的 cv 限定,并且 B 既不是D 也不是 D 的虚拟基类的基类。空指针值(4.10)被转换为目标类型的空指针值。如果“指向 cv1 B 的指针”类型的纯右值指向实际上是 D 类型对象的子对象的 B,则生成的指针指向 D 类型的封闭对象。否则,强制转换的结果是未定义的。

static_cast不能用于转换&meint*sinceMyEnum并且int与继承无关。

于 2014-07-08T18:57:07.273 回答
1

我认为首先的原因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 pointers,但它为您提供了一种使用它们的安全方式reinterpret_cast

于 2014-07-08T13:20:02.760 回答