2011-12-02 21 views
99
#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; 
} 

Il a::LOCAL_A è ciò che l'enum fortemente tipizzato sta cercando di raggiungere, ma c'è una piccola differenza: enumerazioni normali possono essere convertiti in tipo intero, mentre fortemente tipizzati enumerazioni non possono farlo senza un cast.Come convertire automaticamente enum fortemente tipizzato in int?

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

risposta

77

fortemente tipizzato enumerazioni che mirano a risolvere i molteplici problemi e problemi non solo scoping come lei ha citato nella sua domanda:

  1. fornire il tipo di sicurezza, eliminando così la conversione implicita per intero dalla promozione integrale.
  2. Specificare i tipi sottostanti.
  3. Fornire un ambito avanzato.

Quindi, è impossibile convertire implicitamente un enum fortemente tipizzato in numeri interi, o anche il suo tipo sottostante - questa è l'idea. Quindi devi usare static_cast per rendere la conversione esplicita.

Se il vostro unico problema è scoping e si vuole veramente avere la promozione implicita a numeri interi, quindi è meglio usare non fortemente tipizzato enum con lo scopo della struttura è dichiarata.

Speranza che aiuta!

12

No. C'è in modo naturale.

In effetti, una delle motivazioni dietro aver fortemente digitato enum class in C++ 11 è impedire la loro conversione silenziosa a int.

+0

Dai un'occhiata alla risposta di Khurshid Normuradov. Viene il "modo naturale" ed è molto simile a "The C++ Programming Language (4th ed.)". Non viene in un "modo automatico", e questo è positivo. – PapaAtHome

+0

@PapaAtHome Non capisco il vantaggio di questo rispetto a static_cast. Non ci sono molti cambiamenti nella dattilografia o nella pulizia del codice. Qual è il modo naturale qui? Una funzione che restituisce valore? –

+0

@ user2876962 Il vantaggio, per me, è che non è automatico o 'silenzioso' come dice Iammilind. Ciò impedisce al difficile di trovare errori. Puoi ancora fare un cast ma sei costretto a pensarci. In questo modo sai cosa stai facendo. Per me è parte di un'abitudine 'codifica sicura'. Preferisco che le conversioni non vengano eseguite automaticamente. C'è una piccola possibilità che possa introdurre un errore. Parecchi cambiamenti in C++ 11 relativi al sistema di tipo rientrano in questa categoria se me lo chiedi. – PapaAtHome

104

Come altri hanno già detto, non è possibile avere una conversione implicita, e questo è by-design.

Se si desidera 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; 
+0

Sarebbe bene dichiararlo come 'statico'. – ShitalShah

+5

@ShitalShah che non ha senso. –

+0

Non sai cosa intendi. Sto usando questo come metodo statico in questo momento. – ShitalShah

14
#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; 
} 
+2

Ciò non riduce la digitazione o rende il codice più pulito e ha gli effetti collaterali di rendere più difficile trovare tali conversioni implicite nei grandi progetti. Static_cast sarebbe più facile per la ricerca di progetti rispetto a questi costrutti. –

10

Spero che questo tu o qualcun altro aiuta

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

Questo è chiamato "punire tipo" e sebbene supportato da alcuni compilatori non è portabile, come lo standard C++ dice che dopo aver impostato 'un.i' è il" membro attivo "e puoi solo leggere il membro attivo di un sindacato . –

+6

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

+2

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

3

Come molti hanno detto, non v'è alcun modo per convertire automaticamente senza l'aggiunta di spese generali e troppa complessità, ma è possibile ridurre la digitazione di un un po 'e rendilo migliore usando lambda se alcuni cast saranno usati un po' troppo in uno scenario. Ciò aggiungerebbe un po 'di funzione overhead call, ma renderà il codice più leggibile rispetto alle lunghe stringhe static_cast 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; 
    } 
} 
30

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

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

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


Aggiornamento
Ciò appare anche in efficace moderna C++ da Scott Meyers. Vedere l'articolo 10 (è dettagliato nelle pagine finali dell'articolo all'interno della mia copia del libro).

4

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

Io personalmente uso unario operator+ per la conversione da classi enum al tipo di fondo:

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 piccolo "in testa digitando":

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

Dove io in realtà utilizza una macro per creare enumerazioni e le funzioni dell'operatore 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); } 
4

Questo sembra impossibile con il nativo enum class, ma probabilmente si può deridere un enum class con un 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 è principalmente equivalente t all'originale enum class. È possibile restituire direttamente b::B1 per una funzione con tipo di ritorno b. Puoi fare switch case con esso, ecc.

E nello spirito di questo esempio è possibile utilizzare i modelli (possibilmente insieme ad altre cose) per generalizzare e prendere in giro qualsiasi possibile oggetto definito dalla sintassi enum class.

3

La risposta breve è che non è possibile, come indicato nei post precedenti. Ma per il mio caso, ho semplicemente non volevo ingombrare lo spazio dei nomi, ma hanno ancora conversioni implicite, per cui ho appena fatto:

#include <iostream> 

using namespace std; 

namespace Foo { 
    enum { bar, baz }; 
} 

int main() { 
    cout << Foo::bar << endl; // 0 
    cout << Foo::baz << endl; // 1 
    return 0; 
} 

La namespacing sorta di aggiunge uno strato di tipo di sicurezza, mentre io non ho cast statico qualsiasi valore enum al tipo sottostante.

Problemi correlati