Bagaimana cara mengonversi enum yang sangat diketik secara otomatis ke int?

165
#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;
}

Inilah a::LOCAL_Ayang coba dicapai oleh enum yang diketik dengan kuat, tetapi ada perbedaan kecil: enum yang normal dapat dikonversi menjadi tipe bilangan bulat, sedangkan enum yang diketik sangat tidak dapat melakukannya tanpa gips.

Jadi, apakah ada cara untuk mengubah nilai enum yang sangat diketik menjadi tipe integer tanpa gips? Jika ya, bagaimana?

BЈовић
sumber

Jawaban:

134

Enum yang diketik dengan kuat bertujuan untuk memecahkan banyak masalah dan tidak hanya masalah pelingkupan seperti yang Anda sebutkan dalam pertanyaan Anda:

  1. Memberikan keamanan tipe, sehingga menghilangkan konversi implisit ke integer dengan promosi integral.
  2. Tentukan tipe yang mendasarinya.
  3. Berikan pelingkupan yang kuat.

Jadi, tidak mungkin untuk secara implisit mengubah enum yang diketik dengan kuat menjadi bilangan bulat, atau bahkan tipe yang mendasarinya - itulah idenya. Jadi, Anda harus menggunakan static_castuntuk membuat konversi eksplisit.

Jika satu-satunya masalah Anda adalah pelingkupan dan Anda benar-benar ingin memiliki promosi implisit ke integer, maka Anda lebih baik menggunakan enum yang tidak diketik dengan kuat dengan cakupan struktur yang dideklarasikan.

LF
sumber
2
Itu contoh aneh lain dari 'kami tahu lebih baik apa yang ingin Anda lakukan' dari pencipta C ++. Enum konvensional (gaya lama) memiliki banyak manfaat seperti konversi implisit ke indeks, penggunaan bitwise operasi dll. Gaya baru enum menambahkan hal pelingkupan yang sangat hebat, tetapi ... Anda tidak dapat menggunakan hal itu saja (bahkan dengan eksplisit spesifikasi tipe yang mendasarinya!). Jadi sekarang Anda terpaksa menggunakan enum gaya lama dengan trik seperti memasukkannya ke dalam struct atau membuat workaround paling jelek untuk enum baru seperti membuat wrapper sendiri di sekitar std :: vector hanya untuk mengatasi hal CAST itu. tidak ada komentar
avtomaton
152

Seperti yang orang lain katakan, Anda tidak dapat memiliki konversi tersirat, dan itu adalah desain-samping.

Jika mau, Anda dapat menghindari kebutuhan untuk menentukan jenis yang mendasari pemain.

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

Versi C ++ 14 dari jawaban yang diberikan oleh R. Martinho Fernandes adalah:

#include <type_traits>

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

Seperti dengan jawaban sebelumnya, ini akan bekerja dengan segala jenis enum dan tipe yang mendasarinya. Saya telah menambahkan noexceptkata kunci karena tidak akan pernah membuang pengecualian.


Pembaruan
Ini juga muncul di C ++ Modern Efektif oleh Scott Meyers . Lihat item 10 (itu dirinci di halaman akhir item dalam salinan buku saya).

Kerangka Kelas
sumber
18
#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
sumber
3
Ini tidak mengurangi mengetik atau membuat kode lebih bersih dan memiliki efek samping membuatnya lebih sulit untuk menemukan konversi tersirat dalam proyek-proyek besar. Static_cast akan lebih mudah untuk mencari lebar proyek daripada konstruksi ini.
Atul Kumar
3
@AtulKumar Bagaimana mencari static_cast lebih mudah daripada mencari to_enum?
Johann Gerell
1
Jawaban ini membutuhkan penjelasan dan dokumentasi.
Lightness Races in Orbit
17

Tidak. Tidak ada cara alami .

Bahkan, salah satu motivasi di balik mengetik sangat kuat enum classdi C ++ 11 adalah untuk mencegah konversi diam mereka int.

iammilind
sumber
Lihatlah balasan dari Khurshid Normuradov. Itu datang 'cara alami' dan seperti yang dimaksud dalam 'Bahasa Pemrograman C ++ (4th ed.)'. Itu tidak datang dengan 'cara otomatis', dan itu bagus tentang hal itu.
PapaAtHome
@ PapaAtHome Saya tidak mengerti manfaat dari static_cast itu. Tidak banyak perubahan dalam pengetikan atau kebersihan kode. Apa cara alami di sini? Nilai pengembalian fungsi?
Atul Kumar
1
@ user2876962 Manfaatnya, bagi saya, adalah tidak otomatis atau 'diam' seperti yang dikatakan Iammilind. Itu mencegah kesulitan menemukan kesalahan. Anda masih bisa melakukan gips tetapi Anda dipaksa untuk memikirkannya. Dengan begitu Anda tahu apa yang Anda lakukan. Bagi saya itu adalah bagian dari kebiasaan 'pengkodean yang aman'. Saya lebih suka bahwa tidak ada konversi yang tidak dilakukan secara otomatis apakah ada peluang yang dapat menyebabkan kesalahan. Cukup banyak perubahan dalam C ++ 11 yang terkait dengan tipe sistem termasuk dalam kategori ini jika Anda bertanya kepada saya.
PapaAtHome
17

Alasan tidak adanya konversi implisit (dengan desain) diberikan dalam jawaban lain.

Saya pribadi menggunakan unary operator+untuk konversi dari kelas enum ke tipe dasarnya:

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

Yang memberi sedikit "pengetikan overhead":

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

Di mana saya benar-benar menggunakan makro untuk membuat enum dan fungsi operator dalam satu kesempatan.

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); }
Pixelchemist
sumber
13

Semoga ini bisa membantu Anda atau orang lain

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
sumber
33
Ini disebut "type punning" dan meskipun didukung oleh beberapa kompiler tidak portabel, karena standar C ++ mengatakan bahwa setelah Anda menetapkan un.iitu adalah "anggota aktif" dan Anda hanya dapat membaca anggota aktif serikat.
Jonathan Wakely
6
@ JonathanWakely Secara teknis Anda benar, tapi saya belum pernah melihat kompiler di mana ini tidak bekerja dengan andal. Hal-hal seperti ini, serikat anonim, dan #pragma dulu adalah standar defacto.
BigSandwich
5
Mengapa menggunakan sesuatu yang dilarang secara standar, ketika para pemeran sederhana akan melakukannya? Ini salah.
Paul Groke
1
Secara teknis benar atau tidak, bagi saya cara ini lebih mudah dibaca daripada solusi lain yang ditemukan di sini. Dan yang lebih penting bagi saya, dapat digunakan untuk menyelesaikan tidak hanya serialisasi, tetapi juga deserialisasi kelas enum dengan mudah, dan format yang mudah dibaca.
Marcin Waśniowski
6
Saya benar-benar putus asa bahwa ada orang yang menganggap perilaku tidak jelas yang berantakan ini "jauh lebih mudah dibaca" daripada yang sederhana static_cast.
underscore_d
13

Jawaban singkatnya adalah Anda tidak bisa seperti yang ditunjukkan tulisan di atas. Tetapi untuk kasus saya, saya tidak ingin mengacaukan namespace tetapi masih memiliki konversi implisit, jadi saya hanya melakukannya:

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

Jenis namespacing menambahkan lapisan keamanan tipe sementara saya tidak harus statis melemparkan nilai enum apa pun ke tipe yang mendasarinya.

solstice333
sumber
3
Ini tidak menambahkan keamanan tipe apa pun (memang, Anda baru saja menghapus keamanan jenis) - itu hanya menambahkan pelingkupan nama.
Lightness Races in Orbit
@LightnessRacesinOrbit ya saya setuju. Aku berbohong. Secara teknis, tepatnya, jenisnya hanya di bawah ruang nama / ruang lingkup dan sepenuhnya memenuhi syarat untuk Foo::Foo. Anggota dapat diakses sebagaimana Foo::bardan Foo::bazdan dapat secara tidak langsung dicor (dan tidak banyak jenis keamanan). Mungkin lebih baik untuk hampir selalu menggunakan kelas enum terutama jika memulai proyek baru.
solstice333
6

Ini tampaknya tidak mungkin dengan yang asli enum class, tetapi mungkin Anda dapat mengejek enum classdengan class:

Pada kasus ini,

enum class b
{
    B1,
    B2
};

akan setara dengan:

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

Ini sebagian besar setara dengan aslinya enum class. Anda dapat langsung kembali b::B1dalam suatu fungsi dengan tipe pengembalian b. Anda bisa melakukannya switch case, dll.

Dan dalam semangat contoh ini Anda dapat menggunakan template (mungkin bersama-sama dengan hal-hal lain) untuk menggeneralisasi dan mengolok-olok objek yang mungkin ditentukan oleh enum classsintaksis.

Colliot
sumber
tetapi B1 dan B2 harus didefinisikan di luar kelas ... atau ini tidak dapat digunakan untuk case - header.h <- class b - main.cpp <---- myvector.push_back (B1)
Fl0
Tidakkah seharusnya itu menjadi "static constexpr b" dan bukannya "static constexpr int '? Jika tidak, b :: B1 hanyalah sebuah int tanpa jenis pengamanan sama sekali
Some Guy
4

Seperti yang banyak dikatakan, tidak ada cara untuk secara otomatis mengkonversi tanpa menambahkan biaya overhead dan terlalu banyak kerumitan, tetapi Anda dapat mengurangi mengetik sedikit dan membuatnya terlihat lebih baik dengan menggunakan lambdas jika beberapa pemain akan digunakan sedikit banyak dalam skenario. Itu akan menambahkan sedikit fungsi panggilan overhead, tetapi akan membuat kode lebih mudah dibaca dibandingkan dengan string static_cast yang panjang seperti yang dapat dilihat di bawah ini. Ini mungkin bukan proyek yang berguna luas, tetapi hanya kelas lebar.

#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
sumber
2

Komite C ++ mengambil satu langkah ke depan (pelingkupan enums keluar dari namespace global) dan lima puluh langkah mundur (tidak ada peluruhan tipe enum untuk integer). Sayangnya, enum classtidak bisa digunakan jika Anda membutuhkan nilai enum dengan cara non-simbolik.

Solusi terbaik adalah tidak menggunakannya sama sekali, dan sebaliknya enum diri Anda menggunakan namespace atau struct. Untuk tujuan ini, mereka dapat dipertukarkan. Anda perlu mengetikkan sedikit ekstra saat merujuk pada tipe enum itu sendiri, tetapi itu kemungkinan tidak akan sering.

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
sumber