126

Is there a way to convert an enum class field to the underlying type? I thought this would be automatic, but apparently not.

enum class my_fields : unsigned { field = 1 };

unsigned a = my_fields::field;

That assignment is being rejected by GCC. error: cannot convert 'my_fields' to 'unsigned int' in assignment.

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 5
    If you want to convert to underlying type then use `enum`. – Pubby Jan 29 '13 at 18:11
  • 1
    FYI, this rule is defined in `[C++11: 7.2/9]`. – Lightness Races in Orbit Aug 28 '13 at 15:22
  • 7
    @Pubby Sadly unscoped 'enum' pollutes the outer scope with all the enumerants. Alas there is no best of both worlds (as of C++14 anyway) which cleanly nests scope while also implicitly converting to the base type (which is rather inconsistent with how C++ handles other class inheritance, when you pass a more derived type by value or reference to a function taking a base type). – Dwayne Robinson Oct 18 '15 at 04:31
  • 6
    @DwayneRobinson Yes there is. Stick an unscoped enum inside a struct or (more preferably) a namespace. Thus it is scoped and still has the implicit int conversion. (Though I'd be sure to think twice about why you need to convert to an int and perhaps consider if there's a better approach.) – Pharap Sep 04 '17 at 12:32
  • @Pharap So, if you needed some named constants, you would prefer a namespace with a bunch of `constexpr Vn = ;` over an enum class? Weird :) – BitTickler Sep 04 '22 at 10:37
  • @BitTickler At what point did I say that? I pointed out that such a thing **is** possible **if** someone needs both scoping and an implicit `int` conversion, but _at no point_ did I suggest that was a better solution than using an `enum class`. _Please don't put words into other people's mouths._ – Pharap Sep 04 '22 at 18:05

5 Answers5

198

I think you can use std::underlying_type to know the underlying type, and then use cast:

#include <type_traits> //for std::underlying_type

typedef std::underlying_type<my_fields>::type utype;

utype a = static_cast<utype>(my_fields::field);

With this, you don't have to assume the underlying type, or you don't have to mention it in the definition of the enum class like enum class my_fields : int { .... } or so.

You can even write a generic convert function that should be able to convert any enum class to its underlying integral type:

template<typename E>
constexpr auto to_integral(E e) -> typename std::underlying_type<E>::type 
{
   return static_cast<typename std::underlying_type<E>::type>(e);
}

then use it:

auto value = to_integral(my_fields::field);

auto redValue = to_integral(Color::Red);//where Color is an enum class!

And since the function is declared to be constexpr, you can use it where constant expression is required:

int a[to_integral(my_fields::field)]; //declaring an array

std::array<int, to_integral(my_fields::field)> b; //better!
NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    Now that we're in the future: `template auto to_integral(T e) { return static_cast>(e); }` – Ryan Haining Nov 21 '19 at 00:42
  • 1
    @RyanHaining: Thanks. (BTW, you have `constexpr` as well in the future; in fact much more powerful one than what I had in 2013 :P) – Nawaz Nov 21 '19 at 05:00
  • I had to add `typename` i.e. replace `typedef std::underlying_type::type utype;` with `typedef typename std::underlying_type::type utype;`for gcc `c++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0` using `-std=gnu++11` – brewmanz Feb 28 '22 at 01:55
  • 1
    @brewmanz: Yes, you have to do that only if `my_fields` in your case is a template parameter. I'm sure that is the case with you. – Nawaz Mar 02 '22 at 09:22
44

You cannot convert it implicitly, but an explicit cast is possible:

enum class my_fields : unsigned { field = 1 };

// ...

unsigned x = my_fields::field; // ERROR!
unsigned x = static_cast<unsigned>(my_fields::field); // OK

Also mind the fact, that the semicolon should be after the closed curly brace in your enum's definition, not before.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
8

With C++23 you'll finally get a library function for this:

std::to_underlying

It is already implemented in the standard libraries of GCC 11, Clang 13, and MSVC 19.30 (aka 2022 17.0).

Until you're able to use C++23 I recommend you (re)name any custom implementation to to_underlying and place it between a #if !defined(__cpp_lib_to_underlying) #endif block, which is the associated feature test macro. This way you can simply ditch the code at some point in the future when C++23 becomes available for you.

Stacker
  • 1,080
  • 14
  • 20
  • It feels like an ad-hoc bandaid, though, does it not? The base class analogy also works without `std::base_type` or something like that. Once they nicked the syntax from that analogy, they failed to also re-use the semantics. – BitTickler Sep 04 '22 at 18:42
1

As others have pointed out there is no implicit cast, but you can use an explicit static_cast. I use the following helper functions in my code to convert to and from an enum type and its underlying class.

    template<typename EnumType>
    constexpr inline decltype(auto) getIntegralEnumValue(EnumType enumValue)
    {
        static_assert(std::is_enum<EnumType>::value,"Enum type required");
        using EnumValueType = std::underlying_type_t<EnumType>;
        return static_cast<EnumValueType>(enumValue);
    }

    template<typename EnumType,typename IntegralType>
    constexpr inline EnumType toEnum(IntegralType value)
    {
        static_assert(std::is_enum<EnumType>::value,"Enum type required");
        static_assert(std::is_integral<IntegralType>::value, "Integer required");
        return static_cast<EnumType>(value);
    }

    template<typename EnumType,typename UnaryFunction>
    constexpr inline void setIntegralEnumValue(EnumType& enumValue, UnaryFunction integralWritingFunction)
    {
        // Since using reinterpret_cast on reference to underlying enum type is UB must declare underlying type value and write to it and then cast it to enum type
        // See discussion on https://stackoverflow.com/questions/19476818/is-it-safe-to-reinterpret-cast-an-enum-class-variable-to-a-reference-of-the-unde

        static_assert(std::is_enum<EnumType>::value,"Enum type required");

        auto enumIntegralValue = getIntegralEnumValue(enumValue);
        integralWritingFunction(enumIntegralValue);
        enumValue = toEnum<EnumType>(enumIntegralValue);
    }

Usage code

enum class MyEnum {
   first = 1,
   second
};

MyEnum myEnum = MyEnum::first;
std::cout << getIntegralEnumValue(myEnum); // prints 1

MyEnum convertedEnum = toEnum(1);

setIntegralEnumValue(convertedEnum,[](auto& integralValue) { ++integralValue; });
std::cout << getIntegralEnumValue(convertedEnum); // prints 2
GameSalutes
  • 1,762
  • 2
  • 18
  • 24
0

I find the following function underlying_cast useful when having to serialise enum values correctly.

namespace util
{

namespace detail
{
    template <typename E>
    using UnderlyingType = typename std::underlying_type<E>::type;

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

}   // namespace util.detail


template <typename E, typename = detail::EnumTypesOnly<E>>
constexpr detail::UnderlyingType<E> underlying_cast(E e) {
    return static_cast<detail::UnderlyingType<E>>(e);
}

}   // namespace util

enum SomeEnum : uint16_t { A, B };

void write(SomeEnum /*e*/) {
    std::cout << "SomeEnum!\n";
}

void write(uint16_t /*v*/) {
    std::cout << "uint16_t!\n";
}

int main(int argc, char* argv[]) {
    SomeEnum e = B;
    write(util::underlying_cast(e));
    return 0;
}
James
  • 9,064
  • 3
  • 31
  • 49