Bisakah kelas enum diubah menjadi tipe yang mendasari?

112

Apakah ada cara untuk mengonversi enum classbidang menjadi tipe yang mendasari? Saya pikir ini akan otomatis, tetapi ternyata tidak.

enum class my_fields : unsigned { field = 1 };

unsigned a = my_fields::field;

Tugas itu ditolak oleh GCC. error: cannot convert 'my_fields' to 'unsigned int' in assignment.

edA-qa mort-ora-y
sumber
4
Jika Anda ingin mengubah ke tipe yang mendasari, gunakan enum.
Pubby
1
FYI, aturan ini didefinisikan di [C++11: 7.2/9].
Lightness Races di Orbit
5
@Pubby Sayangnya 'enum' yang tidak tercakup mencemari lingkup luar dengan semua enumerant. Sayangnya tidak ada yang terbaik dari kedua dunia (seperti pada C ++ 14) yang secara rapi menumpuk cakupan sementara juga secara implisit mengonversi ke tipe dasar (yang agak tidak konsisten dengan cara C ++ menangani pewarisan kelas lain, ketika Anda meneruskan tipe yang lebih diturunkan oleh nilai atau referensi ke fungsi yang menggunakan tipe dasar).
Dwayne Robinson
2
@DwayneRobinson Ya ada. Tempelkan enum tanpa cakupan di dalam struct atau (lebih disukai) namespace. Jadi itu tercakup dan masih memiliki konversi int implisit. (Meskipun saya pasti akan berpikir dua kali tentang mengapa Anda perlu beralih ke int dan mungkin mempertimbangkan jika ada pendekatan yang lebih baik.)
Pharap

Jawaban:

178

Saya pikir Anda dapat menggunakan std :: underlying_type untuk mengetahui tipe yang mendasarinya, dan kemudian menggunakan cast:

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

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

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

Dengan ini, Anda tidak perlu mengasumsikan tipe yang mendasari, atau Anda tidak perlu menyebutkannya dalam definisi enum classsuka enum class my_fields : int { .... }atau lebih.

Anda bahkan dapat menulis fungsi konversi umum yang seharusnya dapat mengonversi apa pun enum class menjadi tipe integral yang mendasarinya :

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

lalu gunakan itu:

auto value = to_integral(my_fields::field);

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

Dan karena fungsinya dideklarasikan constexpr, Anda dapat menggunakannya di mana ekspresi konstan diperlukan:

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

std::array<int, to_integral(my_fields::field)> b; //better!
Nawaz
sumber
Sekarang kita berada di masa depan:template <typename T> auto to_integral(T e) { return static_cast<std::underlying_type_t<T>>(e); }
Ryan Haining
1
@RyanHaining: Terima kasih. (BTW, Anda memiliki constexprjuga di masa depan; sebenarnya jauh lebih kuat dari apa yang saya miliki di 2013: P)
Nawaz
41

Anda tidak dapat mengubahnya secara implisit , tetapi cast eksplisit dimungkinkan:

enum class my_fields : unsigned { field = 1 };

// ...

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

Juga perhatikan fakta, bahwa titik koma harus setelah kurung kurawal tertutup dalam definisi enum Anda, bukan sebelumnya.

Andy Prowl
sumber
0

Saya menemukan fungsi berikut underlying_castberguna ketika harus menyusun nilai enum dengan benar.

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
sumber
0

Seperti yang ditunjukkan orang lain, tidak ada pemeran implisit, tetapi Anda dapat menggunakan pemeran eksplisit static_cast. Saya menggunakan fungsi pembantu berikut dalam kode saya untuk mengonversi ke dan dari tipe enum dan kelas yang mendasarinya.

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

Kode penggunaan

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
sumber