Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Cannot initialize enum value from a constant of the same type

For unknown reason I cannot initialize enum value from a constexpr value. Here is my code:

enum class Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(2);

enum class Enum: unsigned int {
  A = 0,
  B = 1,
  C = B,                                   //This works
  D = constant,                            //This FAILS
  E = static_cast<unsigned int>(constant), //This works
  F = Enum::B                              //This works
};

What I cannot understand is why I can write C = B, but cannot write D = constant (B and constant have the same type!)

I still can do E = static_cast<unsigned int>(constant), but it is too verbose (in my real-life code each enum value is initialized by a constexpr function call, and it is hard to put static_cast<unsigned int> everywhere).

like image 259
Anton Sukhinov Avatar asked Sep 02 '20 13:09

Anton Sukhinov


People also ask

Can we have enum containing enum with same constant?

The values assigned to the enum names must be integral constant, i.e., it should not be of other types such string, float, etc. All the enum names must be unique in their scope, i.e., if we define two enum having same scope, then these two enums should have different enum names otherwise compiler will throw an error.

Can enums have the same value?

1. Two enum names can have same value. For example, in the following C program both 'Failed' and 'Freezed' have same value 0.

Can you initialize an enum?

You cannot initialize an enumeration with an enumeration constant from a different enumeration or an integer without an explicit cast.

Can enum values repeat?

Yes. Use GetValues() method in System. Enum class.


1 Answers

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


C++: Cannot initialize enum value from a constant of the same type

First of all, the enum type and its underlying type are not the same, and the enumerator of the former shall be defined with, if any, a constexpr value of the underlying type or a constant expression implicitly convertible to the underlying type.

As covered in the non-normative example of [dcl.enum]/10, there are no implicit conversions between a scoped enum and an integer, not even to its explicitly specified fixed underlying type:

The value of an enumerator or an object of an unscoped enumeration type is converted to an integer by integral promotion. [ Example: [...]

Note that this implicit enum to int conversion is not provided for a scoped enumeration:

enum class Col { red, yellow, green };
int x = Col::red;               // error: no Col to int conversion
Col y = Col::red;
if (y) { }                      // error: no Col to bool conversion

— end example ]

and, as governed by the non-presence of scoped enumerations in the normative text of [conv.integral]/1 and [conv.prom]/4 ([conv.prom]/3 for unscoped enumerations types whose underlying type is not fixed) [emphasis mine]:

[conv.integral]/1

A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type.

[conv.prom]/4

A prvalue of an unscoped enumeration type whose underlying type is fixed ([dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

As such, your program, particularly the enumeration definition D = constant, is ill-formed.

And indeed, if we modify your example such that Enum is changed into a non-scoped enumeration, the program is no longer ill-formed.

enum Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(2);

enum Enum: unsigned int {
  A = 0,
  B = 1,
  C = B,                                  // Ok
  D = constant,                           // Ok, implicit conversion
  E = static_cast<unsigned int>(constant) // Ok
};

int main() { }

Then why C = B in my code works?

Because of the somewhat tricky clause [dcl.enum]/5, which states that

Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration. If the underlying type is fixed, the type of each enumerator prior to the closing brace is the underlying type [...]. If the underlying type is not fixed, the type of each enumerator prior to the closing brace is determined as follows:

  • [...]

In layman terms, the type of each enumerator basically changes depending on whether its viewed from within the definition of the enumeration or from outside of it.

This means that inside of the enum definition, we may use a previously defined enumerator in the definition of one that follows, as they both have the type (ignoring some details in [dcl.enum]/5.1 and [dcl.enum]/5.3 for enumerations where the underlying type is not fixed), no matter is the enum is scoped or not (i.e., no need for implicit conversions), whereas outside of the definition of the enum, these enumerators have the same type as that of the enum itself.

#include <type_traits>

enum class Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(0);

// Forward a constexpr value whilst asserting
// type identity between two type template parameters.
template<typename T, typename U, unsigned int VALUE>
struct assert_and_get_value {
    static_assert(std::is_same_v<T, U>, "");
    static constexpr unsigned int value = VALUE;  
};

enum class Enum: unsigned int {
  A = 1,
  B,

  // C and B here are both of type 'unsigned int'
  C = B,
  D = assert_and_get_value<decltype(C), unsigned int, 5>::value,

  // Note that 'constant', however, in this scope, has type 'Enum'.
  E = assert_and_get_value<decltype(constant), const Enum, 6>::value,
};

// At this point, however, the type of each enumerator
// is the type of the enum.
static_assert(std::is_same_v<decltype(Enum::A), Enum>, "");
static_assert(!std::is_same_v<decltype(Enum::A), unsigned int>, "");

int main() {}
like image 89
dfrib Avatar answered Sep 28 '22 05:09

dfrib