Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

In C++11 we can cast a strongly-typed enum (enum class) to its underlying type. But it seems we cannot cast a pointer to the same:

enum class MyEnum : int {};  int main() {   MyEnum me;    int iv = static_cast<int>(me); // works   int* ip = static_cast<int*>(&me); // "invalid static_cast" } 

I'm trying to understand why this should be: is there something about the enum mechanism that makes it hard or nonsensical to support this? Is it a simple oversight in the standard? Something else?

It seems to me that if an enum type is truly built on top of an integral type as above, we should be able to cast not only the values but also the pointers. We can still use reinterpret_cast<int*> or a C-style cast but that's a bigger hammer than I thought we'd need.

like image 652
John Zwinck Avatar asked Jul 04 '14 04:07

John Zwinck


People also ask

Are enums strongly typed?

Enum Class C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped.

Is enum type safe C++?

Enumerations in C++03 are not sufficiently type-safe and may lead to unintended errors. In spite of being a language-supported feature, enums also have code portability issues due to different ways in which different compilers handle the corner cases of enumerations.

Can enum be string C++?

Here we will see how to map some enum type data to a string in C++. There is no such direct function to do so. But we can create our own function to convert enum to string. We shall create a function that takes an enum value as the argument, and we manually return the enum names as a string from that function.


2 Answers

TL;DR: The designers of C++ don't like type punning.

Others have pointed out why it's not allowed by the standard; I will try to address why the writers of the standard might have made it that way. According to this proposal, the primary motivation for strongly-typed enums was type safety. Unfortunately, type safety means many things to many people. It's fair to assume consistency was another goal of the standards committee, so let's examine type safety in other relevant contexts of C++.

C++ type safety

In C++ in general, types are unrelated unless explicitly specified to be related (through inheritance). Consider this example:

class A {     double x;     int y; };  class B {     double x;     int y; };  void foo(A* a) {     B* b = static_cast<B*>(a); //error } 

Even though A and B have the exact same representation (the standard would even call them "standard-layout types"), you cannot convert between them without a reinterpret_cast. Similarly, this is also an error:

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

Even though we know the only thing in C is an int and you can freely access it, the static_cast fails. Why? It's not explicitly specified that these types are related. C++ was designed to support object-oriented programming, which provides a distinction between composition and inheritance. You can convert between types related by inheritance, but not those related by composition.

Based on the behavior you've seen, it's clear strongly-typed enums are related by composition to their underlying types. Why might this have been the model the standard committee chose?

Composition vs Inheritance

There are many articles on this issue better written than anything I could fit here, but I'll attempt to summarize. When to use composition vs. when to use inheritance is certainly a grey area, but there are many points in favor of composition in this case.

  1. Strongly-typed enums are not intended to be used as integral values. Thus the 'is-a' relationship indicated by inheritance does not fit.
  2. On the highest level, enums are meant to represent a set of discrete values. The fact that this is implemented through assigning an id number to each value is generally not important (unfortunately C exposes and thus enforces this relationship).
  3. Looking back at the proposal, the listed reason for allowing a specified underlying type is to specify the size and signedness of the enum. This is much more of an implementation detail than an essential part of the enum, again favoring composition.

You could argue for days about whether or not inheritance or composition is better in this case, but ultimately a decision had to be made and the behavior was modeled on composition.

like image 121
Falias Avatar answered Sep 18 '22 11:09

Falias


Instead, look at it in a slightly different way. You can't static_cast a long* to int* even if int and long have identical underlying representations. For same same reason an enum based on int is yet treated as a unique, unrelated type to int and as such requires the reinterpret_cast.

like image 21
Mark B Avatar answered Sep 18 '22 11:09

Mark B