Come convertire automaticamente enum fortemente tipizzato in 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;
}

Questo a::LOCAL_Aè ciò che l'enum fortemente tipizzato sta cercando di ottenere, ma c'è una piccola differenza: gli enum normali possono essere convertiti in tipo intero, mentre gli enum fortemente tipizzati non possono farlo senza un cast.

Quindi, c'è un modo per convertire un valore enum fortemente tipizzato in un tipo intero senza cast? Se si, come?

Risposte:


134

Enum fortemente tipizzati che mirano a risolvere molteplici problemi e non solo problemi di scoping come hai menzionato nella tua domanda:

  1. Fornire la sicurezza del tipo, eliminando così la conversione implicita in numero intero mediante la promozione integrale.
  2. Specifica i tipi sottostanti.
  3. Fornire un ambito di applicazione forte.

Pertanto, è impossibile convertire implicitamente un enum fortemente tipizzato in numeri interi o persino nel suo tipo sottostante: questa è l'idea. Quindi devi usare static_castper rendere esplicita la conversione.

Se il tuo unico problema è l'ambito e vuoi davvero avere una promozione implicita per gli interi, allora è meglio usare enum non fortemente tipizzato con l'ambito della struttura in cui è dichiarato.


2
Questo è un altro strano esempio di "sappiamo meglio cosa vuoi fare" dai creatori di C ++. Gli enum convenzionali (vecchio stile) hanno avuto molti vantaggi come la conversione implicita in indici, l'utilizzo senza soluzione di continuità di operazioni bit per bit, ecc. specifica del tipo sottostante!). Quindi ora o sei costretto a usare enum vecchio stile con trucchi come metterli in atto o creare soluzioni più brutte per nuovi enum come creare il tuo wrapper attorno a std :: vector solo per superare quella cosa CAST. nessun commento
avtomaton

152

Come altri hanno già detto, non è possibile avere una conversione implicita, e questo è un progetto.

Se lo desideri, puoi evitare la necessità di specificare il tipo sottostante nel 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;

75

Una versione C ++ 14 della risposta fornita da R. Martinho Fernandes sarebbe:

#include <type_traits>

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

Come nella risposta precedente, questo funzionerà con qualsiasi tipo di enum e tipo sottostante. Ho aggiunto la noexceptparola chiave in quanto non genererà mai un'eccezione.


Aggiornamento
Questo appare anche in Effective Modern C ++ di Scott Meyers . Vedi l'articolo 10 (è dettagliato nelle pagine finali dell'articolo all'interno della mia copia del libro).


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

3
Ciò non riduce la digitazione o rende il codice più pulito e ha gli effetti collaterali di rendere più difficile trovare conversioni così implicite in grandi progetti. Static_cast sarebbe più facile da ricercare nel progetto rispetto a questi costrutti.
Atul Kumar,

3
@AtulKumar In che modo la ricerca di static_cast è più semplice della ricerca di to_enum?
Johann Gerell,

1
Questa risposta richiede spiegazioni e documentazione.
Razze di leggerezza in orbita

17

No. Non esiste un modo naturale .

In effetti, una delle motivazioni alla base della forte digitazione enum classin C ++ 11 è impedire la loro conversione silenziosa in int.


Dai un'occhiata alla risposta di Khurshid Normuradov. Viene dal "modo naturale" ed è molto simile a "Il linguaggio di programmazione C ++ (4a edizione)". Non arriva in un "modo automatico", e questo è positivo.
PapaAtHome

@PapaAtHome Non capisco il vantaggio di questo su static_cast. Non molto cambiamento nella digitazione o pulizia del codice. Qual è il modo naturale qui? Una funzione che restituisce valore?
Atul Kumar,

1
@ user2876962 Il vantaggio, per me, è che non è automatico o 'silenzioso' come dice Iammilind. Ciò impedisce di difficile trovare errori. Puoi ancora fare un cast ma sei costretto a pensarci. In questo modo sai cosa stai facendo. Per me fa parte di un'abitudine di "codifica sicura". Preferisco che nessuna conversione non venga eseguita automaticamente se c'è una possibilità che possa introdurre un errore. Molte modifiche in C ++ 11 relative al sistema di tipi rientrano in questa categoria se me lo chiedi.
PapaAtHome,

17

Il motivo dell'assenza di conversione implicita (in base alla progettazione) è stato fornito in altre risposte.

Personalmente uso unario operator+per la conversione da classi enum al tipo sottostante:

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

Che dà abbastanza poco "overhead di battitura":

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

Dove effettivamente uso una macro per creare enumerazioni e l'operatore funziona in un colpo solo.

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

13

Spero che questo aiuti te o qualcun altro

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

33
Questo si chiama "tipo punning" e sebbene supportato da alcuni compilatori non è portatile, poiché lo standard C ++ afferma che dopo aver impostato un.iquesto è il "membro attivo" e puoi solo leggere il membro attivo di un sindacato.
Jonathan Wakely,

6
@JonathanWakely Sei tecnicamente corretto, ma non ho mai visto un compilatore in cui questo non funziona in modo affidabile. Cose come questa, i sindacati anonimi e #pragma una volta sono standard defacto.
BigSandwich,

5
Perché usare qualcosa che lo standard proibisce esplicitamente, quando farà un semplice cast? Questo è semplicemente sbagliato.
Paul Groke,

1
Tecnicamente corretto o no, per me è molto più leggibile di altre soluzioni trovate qui. E ciò che è più importante per me, può essere utilizzato per risolvere non solo la serializzazione, ma anche la deserializzazione della classe enum con facilità e formato leggibile.
Marcin Waśniowski,

6
Sono assolutamente disperato che ci siano persone che considerano questo comportamento disordinato e indefinito "molto più leggibile" di un semplice static_cast.
underscore_d

13

La risposta breve è che non puoi, come sottolineano i post precedenti. Ma per il mio caso, semplicemente non volevo ingombrare lo spazio dei nomi ma ho ancora conversioni implicite, quindi ho appena fatto:

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

Il tipo di namespace aggiunge un livello di sicurezza del tipo mentre non devo lanciare staticamente alcun valore enum sul tipo sottostante.


3
Non aggiunge alcun tipo di sicurezza (in effetti, hai appena rimosso il tipo di sicurezza) - aggiunge solo l'ambito del nome.
Razze di leggerezza in orbita

@LightnessRacesinOrbit sì, sono d'accordo. Ho mentito. Tecnicamente, per essere esatti, il tipo si trova appena sotto uno spazio / ambito di nomi e si qualifica pienamente Foo::Foo. È possibile accedere ai membri come Foo::bare Foo::bazpossono essere implicitamente espressi (e quindi non molta sicurezza del tipo). Probabilmente è meglio usare quasi sempre le classi enum soprattutto se si avvia un nuovo progetto.
solstizio333

6

Questo sembra impossibile con il nativo enum class, ma probabilmente puoi deridere a enum classcon a class:

In questo caso,

enum class b
{
    B1,
    B2
};

sarebbe equivalente a:

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

Questo è per lo più equivalente all'originale enum class. È possibile tornare direttamente b::B1per in una funzione con tipo restituito b. Tu puoi fareswitch case , ecc.

E nello spirito di questo esempio puoi usare i template (possibilmente insieme ad altre cose) per generalizzare e deridere qualsiasi possibile oggetto definito dalla enum classsintassi.


ma B1 e B2 devono essere definiti al di fuori della classe ... o questo è inutilizzabile per case - header.h <- classe b - main.cpp <---- myvector.push_back (B1)
Fl0

Non dovrebbe essere "static constexpr b" invece di "static constexpr int '? Altrimenti, b :: B1 è solo un int senza alcuna sicurezza di battitura.
Some Guy

4

Come molti hanno detto, non c'è modo di convertire automaticamente senza aggiungere overhead e troppa complessità, ma è possibile ridurre un po 'la digitazione e renderla migliore usando lambdas se alcuni cast verranno usati un po' troppo in uno scenario. Ciò aggiungerebbe un po 'di overhead di funzione, ma renderà il codice più leggibile rispetto alle stringhe static_cast lunghe come si può vedere di seguito. Questo potrebbe non essere utile a livello di progetto, ma solo a livello di classe.

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

2

Il comitato C ++ ha fatto un passo avanti (scoping enum fuori dallo spazio dei nomi globale) e cinquanta passi indietro (nessun decadimento del tipo enum in intero). Purtroppo,enum class semplicemente non è utilizzabile se hai bisogno del valore dell'enum in modo non simbolico.

La soluzione migliore è di non usarlo affatto, e invece di enfatizzare l'enum usando uno spazio dei nomi o una struttura. A tale scopo, sono intercambiabili. Dovrai digitare un piccolo extra quando ti riferisci al tipo di enum stesso, ma probabilmente non lo sarà spesso.

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.