247
#include <iostream>

struct a {
  enum LOCAL_A { A1, A2 };
};
enum class b { B1, B2 };

int foo(int input) { return input; }

int main(void) {
  std::cout << foo(a::A1) << std::endl;
  std::cout << foo(static_cast<int>(b::B2)) << std::endl;
}

The a::LOCAL_A is what the strongly typed enum is trying to achieve, but there is a small difference : normal enums can be converted into integer type, while strongly typed enums can not do it without a cast.

So, is there a way to convert a strongly typed enum value into an integer type without a cast? If yes, how?

Jonas Stein
  • 6,826
  • 7
  • 40
  • 72
BЈовић
  • 62,405
  • 41
  • 173
  • 273

13 Answers13

207

As others have said, you can't have an implicit conversion, and that's by-design.

If you want you can avoid the need to specify the underlying type in the cast.

template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
    return static_cast<typename std::underlying_type<E>::type>(e);
}

std::cout << foo(to_underlying(b::B2)) << std::endl;
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 11
    According to [cppreference.com](https://en.cppreference.com/w/cpp/utility/to_underlying), C++23 will have a `std::to_underlying()` function template which does exactly the above. – adentinger May 27 '22 at 21:11
173

Strongly typed enums aiming to solve multiple problems and not only scoping problem as you mentioned in your question:

  1. Provide type safety, thus eliminating implicit conversion to integer by integral promotion.
  2. Specify underlying types.
  3. Provide strong scoping.

Thus, it is impossible to implicitly convert a strongly typed enum to integers, or even its underlying type - that's the idea. So you have to use static_cast to make conversion explicit.

If your only problem is scoping and you really want to have implicit promotion to integers, then you better off using not strongly typed enum with the scope of the structure it is declared in.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 64
    That's another weird example of 'we know better what you want to do' from C++ creators. Conventional (old-style) enums had tons of benefits like implicit conversion to indexes, seamless using of bitwise operations etc.. The new style enums added a really great scoping thing, but... You cannot use just that thing (even with explicit underlying type specification!). So now you're either forced to use old style enums with tricks like putting them into struct or create ugliest workarounds for new enums like creating your own wrapper around std::vector just to overcome that CAST thing. no comments – avtomaton Jun 10 '20 at 12:35
  • 3
    @avtomaton: You should be aware that regular enums *also* have scoping in C++11. Yes, they dump their enumerators out into the global scope (for backwards compatibility reasons), but you are permitted to access them through the scope of their enumeration name. "*create ugliest workarounds for new enums like creating your own wrapper around std::vector just to overcome that CAST thing*" Or... just type `static_cast`. You'll make your code make a lot more sense. – Nicol Bolas Aug 02 '20 at 22:19
  • 9
    @NicolBolas You are permitted to access the regular enums through the scope even before c++11, but you are also permitted to access them without it as well even in c++11. Using `static_cast` just makes the code to look ugly and less readable. Why should I do that if I explicitly specify the underlying type? That was so elegant and beautiful solutions with old-style enums, and the problem was their scope. The new one added scope, but removed the power of binary masks and direct value comparison. For me that's weird. And the amount of that kind of questions clearly shows that not only for me – avtomaton Aug 04 '20 at 23:03
  • 4
    @avtomaton: "*You are permitted to access the regular enums through the scope even before c++11*" [No, you can't.](https://gcc.godbolt.org/z/47bP4K) – Nicol Bolas Aug 04 '20 at 23:08
  • 1
    @NicolBolas my bad, you're right, thank you for pointing on that. But that backward compatibility in c++11 encourages to use it without the scope (I've seen tons of that code with and without the scope across the sources). I'm trying to say that there is no human way to use integer-based enums supporting integer operations without any hack (like explicit casts, or wrapping in a struct, or whatever). And I still have to use old enums (and I'm wrapping them in a struct to FORCE using that artificial scope name) – avtomaton Aug 04 '20 at 23:22
  • 16
    What is the point of being able to say enum class MyEnum : int if you then still have to cast MyEnum::Value1 as an int when using it as an int? Why even have the ability to specify the type of the values if still you have to afterwards explicitly cast it to that type anyway? I don't get it. – Shavais May 28 '21 at 17:25
  • @Shavais, the point is to specify the memory size needed to store the enum. – Alex O Nov 22 '22 at 14:58
  • @AlexO Alright, fair enough, but shouldn't a :int be implicitly convertible to an int? – Shavais Nov 23 '22 at 07:11
  • 1
    @Shavais, that would defeat the purpose (will allow comparing values from unrelated enums, etc.). If you only want the scope, you can surround a regular `enum` by a `struct`. – Alex O Nov 23 '22 at 18:06
  • @Alex Defeat the purpose? Interesting, so with struct Flowers { enum Lilly = 0, Poppy = 1; } ; .. does it deduce the size? The values (in this case) seem to be usable as ints. So with enum class, even if you specify the value type, still the type of an enum class value is considered unique.. – Shavais Nov 28 '22 at 03:05
  • 1
    @Shavais, the main purpose of the scoped enum is to prohibit the implicit conversion. C++03 enums can be made "scoped" by wrapping them in a struct, and scoped enums can be accessed "unscoped" in C++20 with `using enum`. The underlying type can be specified for unscoped enums since C++11. So the lack of implicit conversion is the only distinguishing feature left, thus "defeats the purpose". – Alex O Dec 01 '22 at 23:11
126

A C++14 version of the answer provided by R. Martinho Fernandes would be:

#include <type_traits>

template <typename E>
constexpr auto to_underlying(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

As with the previous answer, this will work with any kind of enum and underlying type. I have added the noexcept keyword as it will never throw an exception.


Update
This also appears in Effective Modern C++ by Scott Meyers. See item 10 (it is detailed in the final pages of the item within my copy of the book).


A C++23 version would be to use the std::to_underlying function:

#include <utility>

std::cout << std::to_underlying(b::B2) << std::endl;

...or if the underlying type could be a 1 byte type:

std::cout << +(std::to_underlying(b::B2)) << std::endl;
Class Skeleton
  • 2,913
  • 6
  • 31
  • 51
44

The reason for the absence of implicit conversion (by design) was given in other answers.

I personally use unary operator+ for the conversion from enum classes to their underlying type:

template <typename T>
constexpr auto operator+(T e) noexcept
    -> std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T>>(e);
}

Which gives quite little "typing overhead":

std::cout << foo(+b::B2) << std::endl;

Where I actually use a macro to create enums and the operator functions in one shot.

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); }
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
30

Short answer is you can't as above posts point out. But for my case, I simply didn't want to clutter the namespace but still have implicit conversions, so I just did:

#include <iostream>

using namespace std;

namespace Foo {
   enum Foo { bar, baz };
}

int main() {
   cout << Foo::bar << endl; // 0
   cout << Foo::baz << endl; // 1
   return 0;
}

The namespacing sort of adds a layer of type-safety while I don't have to static cast any enum values to the underlying type.

solstice333
  • 3,399
  • 1
  • 31
  • 28
  • 8
    It adds no type safety whatsoever (indeed, you just removed the type safety) - it only adds name scoping. – Lightness Races in Orbit Aug 14 '19 at 13:09
  • @LightnessRacesinOrbit yes I agree. I lied. Technically, to be exact, the type is just underneath a name space/scope and fully qualifies to `Foo::Foo`. Members can be accessed as `Foo::bar` and `Foo::baz` and can be implicitly casted (and so not much type safety). It's probably better to almost always use enum classes especially if starting a new project. – solstice333 Oct 22 '19 at 01:16
  • 1
    I'm OK with this method, if the code really needs enums that you can treat as integers, `static_cast<>` actually hurts type safety, because it's powerful and might convert something it shouldn't. – jrh Nov 26 '20 at 15:49
  • Also another issue with `enum class` is that, well, you have to get values into this enum somehow. If you get it from file, user input, the internet, etc... it's likely going to be an `int`-ish value, so the only time you can really avoid `static_cast` for `enum class` is if the enum is only ever set using `something = SomeEnum::A`. I can't really think of many use cases which wouldn't at least require somebody to load/save their data to file. – jrh Nov 29 '20 at 19:37
  • Helped my case where I specifically used enum values as array indicies, where two enums have very similiar/identical names. – Lasse Meyer Jan 26 '21 at 17:10
  • 1
    Interesting. This solves my problem: I only needed a name-spaced symbolic name, but otherwise it had to be compatible with an int. Now I have to figure out in what circumstances you really want to have an enum also be its own class. – Victor Eijkhout Jul 09 '21 at 15:49
  • I use this in the following way: `namespace FooSth { enum Foo {...}; } using Foo = FooSth::Foo;` So then I can access the enum type with just its own name, without having to specify the namespace, but the enum members stay contained. – LeonTheProfessional Sep 30 '22 at 11:22
25

No. There is no natural way.

In fact, one of the motivations behind having strongly typed enum class in C++11 is to prevent their silent conversion to int.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 1
    Have a look at the reply from Khurshid Normuradov. It comes the 'natural way' and is much as intended in 'The C++ Programming Language (4th ed.)'. It does not come in an 'automatic way', and that is good about it. – PapaAtHome Jan 10 '15 at 16:32
  • 1
    @PapaAtHome I do not understand the benefit of that over static_cast. Not much change in the typing or code cleanness. What is natural way here? A function returning value? – Atul Kumar Mar 20 '15 at 20:56
  • 1
    @user2876962 The benefit, for me, is that it is not automatic or 'silent' as Iammilind puts it. That prevents dificult to find errors. You can still do a cast but you are forced to think about it. That way you know what you're doing. To me it is part of a 'safe coding' habit. I prefer that no conversions are not done automatic is there is a sliver of a chance that it might introduce an error. Quite a few changes in C++11 related to the type system fall in this category if you ask me. – PapaAtHome Mar 23 '15 at 18:52
21
#include <cstdlib>
#include <cstdio>
#include <cstdint>

#include <type_traits>

namespace utils
{

namespace details
{

template< typename E >
using enable_enum_t = typename std::enable_if< std::is_enum<E>::value, 
                                               typename std::underlying_type<E>::type 
                                             >::type;

}   // namespace details


template< typename E >
constexpr inline details::enable_enum_t<E> underlying_value( E e )noexcept
{
    return static_cast< typename std::underlying_type<E>::type >( e );
}   


template< typename E , typename T>
constexpr inline typename std::enable_if< std::is_enum<E>::value &&
                                          std::is_integral<T>::value, E
                                         >::type 
 to_enum( T value ) noexcept 
 {
     return static_cast<E>( value );
 }

} // namespace utils




int main()
{
    enum class E{ a = 1, b = 3, c = 5 };

    constexpr auto a = utils::underlying_value(E::a);
    constexpr E    b = utils::to_enum<E>(5);
    constexpr auto bv = utils::underlying_value(b);

    printf("a = %d, b = %d", a,bv);
    return 0;
}
Khurshid Normuradov
  • 1,564
  • 1
  • 13
  • 20
  • 4
    This does not reduce typing or make code cleaner and has the side effects of making it tougher to find such implicit conversions in big projects. Static_cast would be easier to search project wide than these constructs. – Atul Kumar Mar 20 '15 at 20:54
  • 3
    @AtulKumar How is searching for static_cast easier than searching for to_enum? – Johann Gerell Jan 03 '19 at 14:10
  • 6
    This answer needs some explanation and documentation. – Lightness Races in Orbit Aug 14 '19 at 13:07
  • I agree that having an enum_cast or similar keyword, is better that relying on the static_cast keyword everywhere. When reading the code back I have to look at each static_cast and decide from the context what it might be doing, whereas enum_cast/narrow_cast/up_cast/down_cast all make it clearer what the static_cast is doing, and with the SFINAE clauses, I can be sure the name is what is happening, – Gem Taylor Mar 06 '23 at 15:58
11

Hope this helps you or someone else

enum class EnumClass : int //set size for enum
{
    Zero, One, Two, Three, Four
};

union Union //This will allow us to convert
{
    EnumClass ec;
    int i;
};

int main()
{
using namespace std;

//convert from strongly typed enum to int

Union un2;
un2.ec = EnumClass::Three;

cout << "un2.i = " << un2.i << endl;

//convert from int to strongly typed enum
Union un;
un.i = 0; 

if(un.ec == EnumClass::Zero) cout << "True" << endl;

return 0;
}
vis.15
  • 751
  • 8
  • 18
  • 39
    This is called "type punning" and although supported by some compilers is not portable, as the C++ standard says that after you set `un.i` that is the "active member" and you can only read the active member of a union. – Jonathan Wakely May 02 '14 at 12:49
  • 8
    @JonathanWakely You are technically correct, but I have never seen a compiler where this doesn't work reliably. Stuff like this, anonymous unions, and #pragma once are defacto standards. – BigSandwich Mar 26 '15 at 15:43
  • 13
    Why use something that the standard explicitly forbids, when a simple cast will do? This is just wrong. – Paul Groke Jul 24 '16 at 23:40
  • 1
    Technically correct or not, for me it's way more readable then other solutions found here. And what is more important for me, it can be used to solve not only serialization, but also deserialization of enum class with ease, and readable format. – Marcin Waśniowski Mar 21 '17 at 21:38
  • 12
    I absolutely despair that there are people who consider this messy undefined behaviour "way more readable" than a simple `static_cast`. – underscore_d Sep 15 '18 at 15:09
  • Related: [Type_punning on Wikipedia](https://en.wikipedia.org/wiki/Type_punning); I'm not sure if even the `may_alias` attribute will protect your union method. Currently GCC uses non-strict aliasing, so it could decide to ignore sets to the inactive field in a union. – jrh Apr 11 '20 at 20:03
  • 2
    Fails on my machine, unless I don't use optimizations. I suppose not using optimizations is a small price to pay for doing forbidden things in the language. – Eljay Aug 16 '20 at 14:07
  • @MarcinWaśniowski this is not a well defined way to handle serialization either, I've been down that road, g++ can and will ruin your day (MSVC does not have this optimization so you won't get in trouble if you only use MSVC). If you must type pun `may_alias` can help, but I don't think it will help for `union`s, note that I think this behavior is well defined in C, but not C++, it's a breaking compatibility issue between C and C++. – jrh Nov 29 '20 at 19:43
8

This seems impossible with the native enum class, but probably you can mock a enum class with a class:

In this case,

enum class b
{
    B1,
    B2
};

would be equivalent to:

class b {
 private:
  int underlying;
 public:
  static constexpr int B1 = 0;
  static constexpr int B2 = 1;
  b(int v) : underlying(v) {}
  operator int() {
      return underlying;
  }
};

This is mostly equivalent to the original enum class. You can directly return b::B1 for in a function with return type b. You can do switch case with it, etc.

And in the spirit of this example you can use templates (possibly together with other things) to generalize and mock any possible object defined by the enum class syntax.

Colliot
  • 1,522
  • 3
  • 16
  • 29
  • but B1 and B2 must be defined outside the class... or this is unusable for case - header.h <-- class b - main.cpp <---- myvector.push_back( B1 ) – Fl0 Nov 06 '18 at 17:35
  • Shouldn't that be "static constexpr b" instead of "static constexpr int'? Otherwise, b::B1 is just an int with no typesafety at all. – Some Guy Apr 30 '20 at 20:34
6

The C++ committee took one step forward (scoping enums out of global namespace) and fifty steps back (no enum type decay to integer). Sadly, enum class is simply not usable if you need the value of the enum in any non-symbolic way.

The best solution is to not use it at all, and instead scope the enum yourself using a namespace or a struct. For this purpose, they are interchangable. You will need to type a little extra when refering to the enum type itself, but that will likely not be often.

struct TextureUploadFormat {
    enum Type : uint32 {
        r,
        rg,
        rgb,
        rgba,
        __count
    };
};

// must use ::Type, which is the extra typing with this method; beats all the static_cast<>()
uint32 getFormatStride(TextureUploadFormat::Type format){
    const uint32 formatStride[TextureUploadFormat::__count] = {
        1,
        2,
        3,
        4
    };
    return formatStride[format]; // decays without complaint
}
Anne Quinn
  • 12,609
  • 8
  • 54
  • 101
  • 1
    Nice explanation. Is there any single advantage of enum class over enums within namespace or a struct? – Sujay Phadke Sep 28 '20 at 04:20
  • Is there any reason to use a `struct` instead of a `namespace` for enclosing the enum? – jrh Nov 26 '20 at 15:41
  • 2
    Oh... I just thought of a reason to use struct instead of namespace, it will prevent you from accidentally defining the same enum "namespace" twice. E.g., if `TextureUploadFormat` was a namespace, you could use that namespace more than once and accidentally merge the namespaces of the two Enums. – jrh Nov 26 '20 at 16:50
  • @jrh, the main reason is that you can't name your enum type optimally with the wrapper namespace technique: on the one hand you want the namespace itself be the enum type name (so you can write `DescriptiveEnumTypeName::Value`), but OTOH how would you name the _actual_ `enum` type then? Whatever you call that, the actual type name will show up in declarations like `DescriptiveEnumTypeName::RealEnumTypeName`, which is... depressing. – Sz. Jun 29 '23 at 15:48
  • Then, if you finally give up hope and surrender to a `struct`, a little bit of reward is that you can now naturally & gradually extend/harden it (with some tedium, which is also depressing, but at least doable) to achieve some of the good stuff of an enum class, if you later wish to. – Sz. Jun 29 '23 at 15:51
5

As many said, there is no way to automatically convert without adding overheads and too much complexity, but you can reduce your typing a bit and make it look better by using lambdas if some cast will be used a bit much in a scenario. That would add a bit of function overhead call, but will make code more readable compared to long static_cast strings as can be seen below. This may not be useful project wide, but only class wide.

#include <bitset>
#include <vector>

enum class Flags { ......, Total };
std::bitset<static_cast<unsigned int>(Total)> MaskVar;
std::vector<Flags> NewFlags;

-----------
auto scui = [](Flags a){return static_cast<unsigned int>(a); };

for (auto const& it : NewFlags)
{
    switch (it)
    {
    case Flags::Horizontal:
        MaskVar.set(scui(Flags::Horizontal));
        MaskVar.reset(scui(Flags::Vertical)); break;
    case Flags::Vertical:
        MaskVar.set(scui(Flags::Vertical));
        MaskVar.reset(scui(Flags::Horizontal)); break;

   case Flags::LongText:
        MaskVar.set(scui(Flags::LongText));
        MaskVar.reset(scui(Flags::ShorTText)); break;
    case Flags::ShorTText:
        MaskVar.set(scui(Flags::ShorTText));
        MaskVar.reset(scui(Flags::LongText)); break;

    case Flags::ShowHeading:
        MaskVar.set(scui(Flags::ShowHeading));
        MaskVar.reset(scui(Flags::NoShowHeading)); break;
    case Flags::NoShowHeading:
        MaskVar.set(scui(Flags::NoShowHeading));
        MaskVar.reset(scui(Flags::ShowHeading)); break;

    default:
        break;
    }
}
Atul Kumar
  • 366
  • 4
  • 15
3

Summary

Question:

is there a way to convert a strongly typed enum value into an integer type without a cast? If yes, how?

Answer:

No, there is not. Strongly typed enums can NOT be converted to integers without an explicit cast. Weak enums can, however, as they will be automatically implicitly cast. So, if you'd like automatic implicit conversion to an int, consider using a C-style weak enum instead (see more on this in the "Going further" section below).

From here (emphasis added): https://en.cppreference.com/w/cpp/language/enum --> under the section "Scoped enumerations":

There are no implicit conversions from the values of a scoped enumerator [AKA: "strong enum"] to integral types, although static_cast may be used to obtain the numeric value of the enumerator.

Going further: discussion of weak (C-style) vs strong (C++ enum class) enum types in C++

In C++ there are two types of enums:

  1. "unscoped", "regular", "weak", "weakly typed", or "C-style" enums, and
  2. "scoped", "strong", "strongly typed", "enum class", or "C++-style" enums.

"Scoped" enums, or "strong" enums, give two additional "features" beyond what "regular" enums give you.

Scoped enums:

  1. don't allow implicit casting from the enum type to an integer type (so you can't do what you want to do implicitly!), and
  2. they "scope" the enum so that you have to access the enum through its enum type name.

1. Example of an enum class (available only in C++):

// enum class (AKA: "strong" or "scoped" enum)
enum class my_enum
{
    A = 0,
    B,
    C,
};

my_enum e = my_enum::A; // scoped through `my_enum::`
e = my_enum::B;

// NOT ALLOWED!:
//   error: cannot convert ‘my_enum’ to ‘int’ in initialization
// int i = e; 

// But explicit casting works just fine!:
int i1 = static_cast<int>(e); // explicit C++-style cast 
int i2 = (int)e;              // explicit C-style cast 

The first "feature" may actually be something you don't want, in which case you just need to use a regular C-style enum instead! And the nice thing is: you can still "scope" or "namespace" the enum, as has been done in C for decades, by simply prepending its name with the enum type name, like this:

2. Example of a regular enum (available in both C and C++):

// regular enum (AKA: "weak" or "C-style" enum)
enum my_enum
{
    // C-style-scoped through the `MY_ENUM_` prefix
    MY_ENUM_A = 0,
    MY_ENUM_B,
    MY_ENUM_C,
};

my_enum e = MY_ENUM_A; // scoped through `MY_ENUM_`
e = MY_ENUM_B;

// This works fine!
int i = e;

Notice you still get the benefit of "scoping" simply by adding the MY_ENUM_ "scope" to the front of each enum!

3. Both regular enums and enum classes together:

Test the code here: https://onlinegdb.com/BkWGqlqz_.

main.cpp:

#include <iostream>
#include <stdio.h>

// enum class (AKA: "strong" or "scoped" enum [available only in C++, not C])
enum class my_enum
{
    A = 0,
    B,
    C,
};

// regular enum (AKA: "weak" or "C-style" enum [available in BOTH C and C++])
enum my_enum2
{
    MY_ENUM_A = 0,
    MY_ENUM_B,
    MY_ENUM_C,
};


int main()
{
    printf("Hello World\n");

    // 1) scoped enum

    my_enum e = my_enum::A; // scoped through `my_enum::`
    e = my_enum::B;
    
    // IMPLICIT CASTING TO INT IS NOT ALLOWED!
    // int i = e; // "error: cannot convert ‘my_enum’ to ‘int’ in initialization"
    // But this explicit C++-style cast works fine:
    int i1 = static_cast<int>(e); 
    // This explicit C-style cast works fine too, and is easier to read
    int i2 = (int)e;
    
    
    // 2) regular enum 
    
    my_enum2 e2 = MY_ENUM_A; // scoped through `MY_ENUM_`
    e2 = MY_ENUM_B;
    
    // This implicit cast works fine / IS allowed on C-style enums!
    int i3 = e2;
    // These explicit casts are also fine, but explicit casting is NOT 
    // required for regular enums.
    int i4 = static_cast<int>(e2); // explicit C++-style cast 
    int i5 = (int)e2;              // explicit C-style cast 

    return 0;
}

4. How to iterate over enums:

  1. *****[my answer] Full examples of how to iterate over both 1. weakly-typed C-style and 2. scoped, strongly-typed C++ enum class enums: How can I iterate over an enum?
  2. [my Q&A] What are commonly-used ways to iterate over an enum class in C++?
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
2

An extension to the answers from R. Martinho Fernandes and Class Skeleton: Their answers show how to use typename std::underlying_type<EnumType>::type or std::underlying_type_t<EnumType> to convert your enumeration value with a static_cast to a value of the underlying type. Compared to a static_cast to some specific integer type, like, static_cast<int> this has the benefit of being maintenance friendly, because when the underlying type changes, the code using std::underlying_type_t will automatically use the new type.

This, however, is sometimes not what you want: Assume you wanted to print out enumeration values directly, for example to std::cout, like in the following example:

enum class EnumType : int { Green, Blue, Yellow };
std::cout << static_cast<std::underlying_type_t<EnumType>>(EnumType::Green);

If you later change the underlying type to a character type, like, uint8_t, then the value of EnumType::Green will not be printed as a number, but as a character, which is most probably not what you want. Thus, you sometimes would rather convert the enumeration value into something like "underlying type, but with integer promotion where necessary".

It would be possible to apply the unary operator+ to the result of the cast to force integer promotion if necessary. However, you can also use std::common_type_t (also from header file <type_traits>) to do the following:

enum class EnumType : int { Green, Blue, Yellow };
std::cout << static_cast<std::common_type_t<int, std::underlying_type_t<EnumType>>>(EnumType::Green);

Preferrably you would wrap this expression in some helper template function:

template <class E>
constexpr std::common_type_t<int, std::underlying_type_t<E>>
enumToInteger(E e) {
    return static_cast<std::common_type_t<int, std::underlying_type_t<E>>>(e);
}

Which would then be more friendly to the eyes, be maintenance friendly with respect to changes to the underlying type, and without need for tricks with operator+:

std::cout << enumToInteger(EnumType::Green);
Dirk Herrmann
  • 5,550
  • 1
  • 21
  • 47