Un modo semplice per utilizzare le variabili dei tipi enum come stringa in C?


89

Ecco cosa sto cercando di fare:

Sto cercando di scrivere una funzione che farebbe un caso di commutazione simile al seguente:

Invece di definire in ogni caso, c'è un modo per impostarlo usando la variabile enum come sto cercando di fare sopra?

Risposte:


16

Non esiste una soluzione integrata. Il modo più semplice è con un array di char*dove il valore int dell'enumerazione si indicizza in una stringa contenente il nome descrittivo dell'enumerazione. Se ne hai uno sparse enum(uno che non inizia da 0 o presenta lacune nella numerazione) in cui alcune delle intmappature sono abbastanza alte da rendere impraticabile una mappatura basata su array, potresti invece usare una tabella hash.


Espandendo questo, se effettivamente si tratta di un elenco incrementale lineare, puoi semplicemente usare lo strumento macro del tuo editor per registrare e risolvere ciascuno dei nomi in una stringa. È necessaria una piccola digitazione extra e in primo luogo risparmi la necessità di definire. Faccio clic su Registra sull'ultima delle macro copiate, aggiungo una citazione dopo e procedo allo stesso punto nella riga successiva. Mi fermo. Premo run X volte e ne faccio quante sono (o solo un singolo passaggio). Quindi posso avvolgerlo in una matrice di stringhe.
user2262111

70

La tecnica di Creare qualcosa sia un identificatore C che una stringa? può essere utilizzato qui.

Come al solito con queste cose del preprocessore, scrivere e comprendere la parte del preprocessore può essere difficile, e include il passaggio di macro ad altre macro e implica l'uso di operatori # e ##, ma usarli è davvero facile. Trovo questo stile molto utile per enumerazioni lunghe, dove mantenere lo stesso elenco due volte può essere davvero fastidioso.

Codice di fabbrica - digitato una sola volta, solitamente nascosto nell'intestazione:

enumFactory.h:

Fabbrica usata

someEnum.h:

someEnum.cpp:

La tecnica può essere facilmente estesa in modo che le macro XX accettino più argomenti e si possono anche preparare più macro da sostituire a XX per esigenze diverse, simili alle tre che ho fornito in questo esempio.

Confronto con X-Macro utilizzando #include / #define / #undef

Sebbene sia simile a X-Macro che altri hanno menzionato, penso che questa soluzione sia più elegante in quanto non richiede #undefing nulla, il che ti consente di nascondere più cose complicate che in fabbrica il file di intestazione - il file di intestazione è qualcosa che non si tocca affatto quando è necessario definire un nuovo enum, quindi la nuova definizione di enum è molto più breve e pulita.


2
Non sono sicuro di come si possa dire che sia migliore / peggiore di x-macro - questo è x-macro. Il SOME_ENUM(XX)è esattamente un X-macro (per essere precisi, il "modulo utente" che passa la XXfunzione anziché utilizzare #def #undef) e quindi a sua volta l'intero X-MACRO viene quindi passato a DEFINE_ENUM che lo utilizza. Non togliere nulla alla soluzione: funziona bene. Giusto per chiarire che si tratta di un uso delle macro X.
BeeOnRope

1
@BeeOnRope La differenza che noti è significativa e distingue questa soluzione dalle macro X idiomatiche (come gli esempi di Wikipedia ). Il vantaggio del passaggio XXsu reing #defineè che il primo pattern può essere utilizzato nelle macro espansioni. Si noti che le uniche altre soluzioni così concise come questa richiedono tutte la creazione e l'inclusione multipla di un file separato per definire un nuovo enum.
pmttavara

1
Un altro trucco consiste nell'usare il nome enum come nome della macro. Puoi semplicemente scrivere #define DEFINE_ENUM(EnumType) ..., sostituire ENUM_DEF(...)con EnumType(...)e far dire all'utente #define SomeEnum(XX) .... Il preprocessore C si espanderà contestualmente nell'invocazione SomeEnumdella macro se seguito da parentesi e in un normale token altrimenti. (Ovviamente, questo causa problemi se l'utente preferisce usare il SomeEnum(2)cast al tipo enum anziché (SomeEnum)2o static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - certo, se una ricerca rapida è un'indicazione, l'uso più comune di x-macro utilizza un nome di macro interno fisso insieme a #definee #undef. Non sei d'accordo, però, che il "modulo utente" (suggerito ad esempio in fondo a questo articolo ) sia un tipo di x-macro? Certamente l'ho sempre chiamato anche x-macro e nelle basi di codice C in cui sono stato ultimamente è la forma più comune (questa è ovviamente un'osservazione di parte). Tuttavia, potrei aver analizzato l'OP in modo sbagliato.
BeeOnRope

2
@BeeOnRope L'attuale formulazione è il risultato di una modifica, come mi hai convinto allora questa è x-macro, anche se all'epoca era forse una forma meno usata (o almeno una in meno menzionata negli articoli).
Suma

62

3
Questo è il genere di cose per cui cpp è stato creato. +1.
Derrick Turk

6
Questa è una buona risposta, sembra essere la migliore che si possa fare senza utilizzare strumenti speciali, e ho già fatto questo genere di cose; ma ancora non mi sembra mai veramente "giusto" e non mi piace mai farlo ...
Michael Burr

Piccolo cambiamento: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];in defs.hfile - questo dichiarerà la tua tabella dei nomi nei file che utilizzi. (Non riesco a trovare un buon modo per dichiarare la dimensione della tabella, però.) Inoltre, personalmente lascerei il punto e virgola finale, ma i meriti sono discutibili in entrambi i casi.
Chris Lutz

1
@ Bill, perché preoccuparsi typdella linea #define ENUM_END(typ) };?
Pacerier

Non funziona dove voglio che la mia macro sia definita come "ONE = 5"
UKMonkey,

13

C'è sicuramente un modo per farlo: usa le macro X () . Queste macro utilizzano il preprocessore C per costruire enumerazioni, array e blocchi di codice da un elenco di dati di origine. Hai solo bisogno di aggiungere nuovi elementi a #define contenente la macro X (). L'istruzione switch si espanderebbe automaticamente.

Il tuo esempio può essere scritto come segue:

Ci sono modi più efficienti (cioè usare X Macro per creare un array di stringhe e un indice enum), ma questa è la demo più semplice.


8

So che hai un paio di risposte valide, ma conosci l'operatore # nel preprocessore C?

Ti consente di fare questo:


char const *kConstStr[]
Anne van Rossum

6

C o C ++ non fornisce questa funzionalità, anche se ne ho avuto bisogno spesso.

Il codice seguente funziona, sebbene sia più adatto per enumerazioni non sparse.

Con non sparse, intendo non della forma

dal momento che ha enormi lacune in esso.

Il vantaggio di questo metodo è che mette le definizioni delle enumerazioni e delle stringhe vicine l'una all'altra; avere un'istruzione switch in una funzione li spear. Ciò significa che è meno probabile che cambi uno senza l'altro.


6

BACIO. Farai ogni sorta di altre cose di scambio / caso con le tue enumerazioni, quindi perché la stampa dovrebbe essere diversa? Dimenticare un caso nella tua routine di stampa non è un grosso problema se consideri che ci sono circa 100 altri posti in cui puoi dimenticare un caso. Basta compilare -Wall, che avviserà di corrispondenze tra maiuscole e minuscole. Non usare "default" perché questo renderà il passaggio esaustivo e non riceverai avvisi. Invece, lascia che l'interruttore esca e gestisci il caso predefinito in questo modo ...



4

L'utilizzo di boost :: preprocessor rende possibile una soluzione elegante come la seguente:

Passaggio 1: includi il file di intestazione:

Passaggio 2: dichiara l'oggetto enumerazione con la seguente sintassi:

Passaggio 3: utilizza i tuoi dati:

Ottenere il numero di elementi:

Ottenere la stringa associata:

Ottenere il valore enum dalla stringa associata:

Sembra pulito e compatto, senza file aggiuntivi da includere. Il codice che ho scritto all'interno di EnumUtilities.h è il seguente:

Ci sono alcune limitazioni, ad esempio quelle di boost :: preprocessor. In questo caso, l'elenco delle costanti non può essere maggiore di 64 elementi.

Seguendo la stessa logica, potresti anche pensare di creare enumerazioni sparse:

In questo caso, la sintassi è:

L'utilizzo è simile a quello sopra (meno la funzione eName ## 2Enum, che potresti provare a estrapolare dalla sintassi precedente).

L'ho testato su Mac e Linux, ma tieni presente che boost :: preprocessor potrebbe non essere completamente portabile.


3

Unendo alcune delle tecniche qui ho trovato la forma più semplice:


2

Se stai usando gcc, è possibile usare:

Allora chiama per esempio


1

Dai un'occhiata alle idee in Mu Dynamics Research Labs - Blog Archive . L'ho trovato all'inizio di quest'anno - ho dimenticato il contesto esatto in cui l'ho trovato - e l'ho adattato a questo codice. Possiamo discutere i meriti dell'aggiunta di una E all'inizio; è applicabile al problema specifico affrontato, ma non fa parte di una soluzione generale. L'ho nascosto nella mia cartella "vignette", dove conservo frammenti di codice interessanti nel caso li volessi in seguito. Sono imbarazzato nel dire che in quel momento non ho preso nota della provenienza di questa idea.

Intestazione: paste1.h

Fonte di esempio:

Non necessariamente l'uso più pulito al mondo del pre-processore C, ma impedisce di scrivere più volte il materiale.



0

Se l'indice enum è basato su 0, puoi inserire i nomi in una matrice di char * e indicizzarli con il valore enum.



0

Ho creato un semplice su modelli di classe streamable_enumche utilizza flusso operatori <<ed >>e si basa sul std::map<Enum, std::string>:

Utilizzo:


0

Ecco una soluzione che utilizza macro con le seguenti caratteristiche:

  1. scrivi solo una volta ogni valore dell'enumerazione, quindi non ci sono doppi elenchi da mantenere

  2. non mantenere i valori di enum in un file separato che viene successivamente #incluso, così posso scriverlo dove voglio

  3. non sostituire l'enum stesso, voglio comunque avere il tipo di enum definito, ma in aggiunta ad esso voglio essere in grado di mappare ogni nome enum sulla stringa corrispondente (per non influenzare il codice legacy)

  4. la ricerca dovrebbe essere veloce, quindi preferibilmente senza switch-case, per quelle enormi enumerazioni

https://stackoverflow.com/a/20134475/1812866


0

Ho pensato che una soluzione come Boost.Fusion per adattare strutture e classi sarebbe stata carina, a un certo punto avevano persino usato le enumerazioni come sequenza di fusione.

Quindi ho creato solo alcune piccole macro per generare il codice per stampare le enumerazioni. Questo non è perfetto e non ha nulla a che fare con il codice boilerplate generato da Boost.Fusion, ma può essere utilizzato come le macro Boost Fusion. Voglio davvero generare i tipi necessari a Boost.Fusion per integrarsi in questa infrastruttura che consente di stampare i nomi dei membri della struttura, ma questo avverrà in seguito, per ora si tratta solo di macro:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

La vecchia risposta qui sotto è piuttosto brutta, per favore non usarla. :)

Vecchia risposta:

Ho cercato un modo che risolva questo problema senza cambiare troppo la sintassi della dichiarazione di enumerazioni. Sono arrivato a una soluzione che utilizza il preprocessore per recuperare una stringa da una dichiarazione enum stringa.

Sono in grado di definire enumerazioni non sparse in questo modo:

E posso interagire con loro in diversi modi:

Basato sulle seguenti definizioni:

Quando avrò bisogno del supporto per l' enumerazione sparsa e quando avrò più tempo migliorerò le implementazioni to_string e from_string con boost :: xpressive, ma questo costerà in tempo di compilazione a causa dell'importante templating eseguito e l'eseguibile generato è probabilmente sarà molto più grande. Ma questo ha il vantaggio che sarà più leggibile e gestibile di questo brutto codice di manipolazione manuale delle stringhe.: D

Altrimenti ho sempre usato boost :: bimap per eseguire tali mappature tra valore enumerazione e stringa, ma deve essere mantenuto manualmente.


0

Poiché preferisco non utilizzare le macro per tutti i soliti motivi, ho utilizzato una soluzione macro più limitata che ha il vantaggio di mantenere libera la macro di dichiarazione enum. Gli svantaggi includono la necessità di copiare e incollare la definizione della macro per ogni enumerazione e la necessità di aggiungere esplicitamente una chiamata di macro quando si aggiungono valori all'enumerazione.

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.