Можно ли преобразовать класс перечисления в базовый тип?

112

Есть ли способ преобразовать enum classполе в базовый тип? Я думал, что это будет автоматически, но, видимо, нет.

enum class my_fields : unsigned { field = 1 };

unsigned a = my_fields::field;

Это задание отклоняется GCC. error: cannot convert 'my_fields' to 'unsigned int' in assignment.

edA-qa mort-ora-y
источник
4
Если вы хотите преобразовать в базовый тип, используйте enum.
Pubby
1
К вашему сведению, это правило определено в [C++11: 7.2/9].
Гонки легкости на орбите
5
@Pubby К сожалению, "перечисление" без области видимости загрязняет внешнюю область видимости всеми перечислителями. Увы, нет лучшего из обоих миров (по крайней мере, в C ++ 14), который чисто вкладывает область видимости, а также неявно преобразует ее в базовый тип (что довольно несовместимо с тем, как C ++ обрабатывает наследование других классов, когда вы передаете более производный тип с помощью значение или ссылка на функцию, принимающую базовый тип).
Дуэйн Робинсон
2
@DwayneRobinson Да, есть. Вставьте перечисление с незаданной областью внутрь структуры или (что более предпочтительно) пространства имен. Таким образом, он ограничен и по-прежнему имеет неявное преобразование int. (Хотя я обязательно дважды подумаю, почему вам нужно преобразовать в int, и, возможно, подумайте, есть ли лучший подход.)
Pharap

Ответы:

178

Я думаю, вы можете использовать std :: under_type, чтобы узнать базовый тип, а затем использовать приведение:

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

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

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

При этом вам не нужно предполагать базовый тип или упоминать его в определении чего-то enum classподобного enum class my_fields : int { .... }.

Вы даже можете написать общую функцию преобразования, которая должна иметь возможность преобразовывать любое значение enum class в свой базовый интегральный тип:

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);
}

затем используйте это:

auto value = to_integral(my_fields::field);

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

А так как функция объявлена constexpr, вы можете использовать ее там, где требуется постоянное выражение:

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

std::array<int, to_integral(my_fields::field)> b; //better!
Наваз
источник
Теперь, когда мы в будущем:template <typename T> auto to_integral(T e) { return static_cast<std::underlying_type_t<T>>(e); }
Райан
1
@RyanHaining: Спасибо. (Кстати, у вас есть constexpr, а в будущем, на самом деле гораздо более мощным , чем один , что я имел в 2013 году: P)
Nawaz
41

Вы не можете преобразовать его неявно , но возможно явное приведение:

enum class my_fields : unsigned { field = 1 };

// ...

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

Также имейте в виду, что точка с запятой должна стоять после закрытой фигурной скобки в определении вашего перечисления, а не до нее.

Энди Проул
источник
0

Я считаю, что следующая функция underlying_castполезна при правильной сериализации значений перечисления.

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;
}
Джеймс
источник
0

Как указывали другие, неявного приведения нет, но вы можете использовать явное static_cast. Я использую в своем коде следующие вспомогательные функции для преобразования в тип перечисления и его базовый класс и обратно.

    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 /programming/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);
    }

Код использования

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
источник