Come convertire una variabile di tipo enum in una stringa?


128

Come fare printf per mostrare i valori delle variabili che sono di tipo enum? Per esempio:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

e quello di cui ho bisogno è qualcosa di simile

printenum(OS_type, "My OS is %s", myOS);

che deve mostrare una stringa "Linux", non un numero intero.

Suppongo, per prima cosa devo creare una matrice di stringhe indicizzata per valore. Ma non so se questo è il modo più bello per farlo. È possibile affatto?


2
Fai la tua scelta: qui , qui , qui . È un casino, sono tutti semi-duplicati.
rubenvb,

Un altro , questo è più completo.
bit2shift


Tante risposte ma nessuna "enum class"
Vivick il

Risposte:


70

Non esiste davvero un modo meraviglioso di farlo. Basta impostare un array di stringhe indicizzate dall'enum.

Se fai molto output, puoi definire un operatore << che accetta un parametro enum e fa la ricerca per te.


2
Inoltre è possibile verificare in fase di compilazione che l'array contenga il numero previsto di stringhe.
markh44,

2
So di essere in grande minoranza con questo, ma per i programmatori come me che non desiderano fare affidamento su enormi librerie di terze parti e / o codice marco-enigmato per risolvere le carenze native della lingua, trovo che questo sia la soluzione più semplice e pura per lo standard di oggi di gran lunga. +1
Syndog,

13
@Syndog Quindi i 56 enumeratori enumerati nel tuo codice di produzione vengono aggiornati da questo programmatore sotto una forte pressione per rilasciare una funzionalità scaduta e si dimentica di aggiornare quell'array con indice enumato. Passa inosservato, poiché la relativa funzione di stampa viene utilizzata solo dal codice di debug dell'applicazione. 2 mesi dopo, sei il primo ad eseguire effettivamente quel codice di debug: ti dà quindi le informazioni sbagliate, quindi perdi mezza giornata a costruire ipotesi basate su queste informazioni sbagliate, prima di rendersi conto che devi prima eseguire il debug del codice di debug: il il design si basa sulla duplicazione esplicita.
Annuncio N

1
@AdN Quel design è sbagliato. Il mapping da enum a stringa leggibile dall'uomo non dovrebbe essere implementato come una matrice di stringhe indicizzate dal valore enum. La tua esperienza (presumibilmente) mostra perché. Il mapping dovrebbe essere un array esplicito di coppie (enum, string), quindi se dimentichi di aggiungere una voce per il tuo nuovo valore enum, otterrai "???" come output, ma almeno non rovinerà i nomi di tutti gli altri enum.
brewbuck,

8
@AdN il tuo scenario è il motivo per cui preferisco una funzione contenente uno switch (senza clausola predefinita) piuttosto che un array, e per impostare gli switch del compilatore nel file di build in modo da emettere un errore per uno switch su un enum che non copre tutto valori possibili. L'aggiunta di una nuova voce enum senza l'aggiornamento delle relative istruzioni switch causerà un errore di compilazione.
divegeek

131

La soluzione ingenua, ovviamente, è scrivere una funzione per ogni enumerazione che esegue la conversione in stringa:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

Questo, tuttavia, è un disastro di manutenzione. Con l'aiuto della libreria Boost.Preprocessor, che può essere utilizzata sia con il codice C che C ++, puoi facilmente sfruttare il preprocessore e lasciarlo generare questa funzione per te. La macro di generazione è la seguente:

#include <boost/preprocessor.hpp>

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

La prima macro (che inizia con X_) viene utilizzata internamente dalla seconda. La seconda macro genera prima l'enumerazione, quindi genera una ToStringfunzione che accetta un oggetto di quel tipo e restituisce il nome dell'enumeratore come stringa (questa implementazione, per ovvi motivi, richiede che gli enumeratori eseguano il mapping a valori univoci).

In C ++ potresti invece implementare la ToStringfunzione come operator<<sovraccarico, ma penso che sia un po 'più pulito richiedere un " ToString" esplicito per convertire il valore in forma di stringa.

Come esempio di utilizzo, il tuo OS_type enumerazione sarebbe definita come segue:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

Mentre la macro sembra inizialmente come se fosse un sacco di lavoro, e la definizione di OS_type aspetto è piuttosto estranea, ricorda che devi scrivere la macro una volta, quindi puoi usarla per ogni enumerazione. È possibile aggiungere ulteriori funzionalità ad esso (ad esempio, un modulo stringa per enumerare la conversione) senza troppi problemi e risolve completamente il problema di manutenzione, poiché è necessario fornire i nomi solo una volta, quando si richiama la macro.

L'enumerazione può quindi essere utilizzata come se fosse definita normalmente:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

I frammenti di codice in questo post, a partire dal #include <boost/preprocessor.hpp> riga, possono essere compilati come pubblicati per dimostrare la soluzione.

Questa particolare soluzione è per C ++ in quanto utilizza sintassi specifica C ++ (ad es. No typedef enum) e sovraccarico di funzioni, ma sarebbe semplice far funzionare anche questo con C.


7
+1, Il macchinario di implementazione è spaventoso ma l'interfaccia finale è difficile da battere per eleganza.
deft_code

4
C'è comunque un modo per ottenere questo per consentire anche di fornire i valori interi enum. Ad esempio, Windows sarebbe 3, Linux 5 e Apple 7?
Segna il

4
Sì, è possibile modificare (Windows)in (Windows, 3)poi sostituire il BOOST_PP_SEQ_ENUMcon un opportunamente scritta BOOST_PP_SEQ_FOR_EACH. Non ne ho un esempio a portata di mano, ma posso scriverne uno se lo desideri.
James McNellis,

2
@JamesMcNellis Vorrei sicuramente un esempio di un codice che realizza ciò che Mark ha chiesto, saresti così gentile da mostrarci la strada? :)
Omer Raviv,

2
NOTA: il preprocessore boost ha un limite rigido di 256 elementi. Per enum più grandi, è necessaria una soluzione diversa.
dshin,

32

Questo è il blocco pre-processore

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Definizione di enum

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Chiama usando

GetStringOs_type(winblows);

Tratto da qui . Quant'è fico ? :)


1
Questa è l'unica soluzione che funziona quando il tuo enum ha più di 256 elementi.
dshin,

8

Usalo std::map<OS_type, std::string>e popolalo con enum come chiave e rappresentazione della stringa come valori, quindi puoi fare questi:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

7

Il problema con C enums è che non è un tipo proprio, come è in C ++. Un enum in C è un modo per mappare gli identificatori ai valori integrali. Solo quello. Ecco perché un valore enum è intercambiabile con valori interi.

Come indovini correttamente, un buon modo è quello di creare una mappatura tra il valore enum e una stringa. Per esempio:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

Presumevo - apparentemente in modo errato - il linguaggio di programmazione è limitato a C.
Andrew

1
sei un po 'fuori, enum sono tipi in C. Le costanti del tipo di enumerazione integrale sono di tipo inte non del enumtipo attraverso il quale sono definite, è forse quello che volevi dire. Ma non vedo affatto cosa abbia a che fare con la domanda.
Jens Gustedt,

7

Ho combinato le soluzioni James , Howard ed Éder e ho creato un'implementazione più generica:

  • valore int e rappresentazione stringa personalizzata possono essere facoltativamente definiti per ogni elemento enum
  • Viene utilizzata la "classe enum"

Il codice completo è scritto sotto (usare "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" per definire un enum) ( demo online ).

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

Questa è la risposta migliore finora
Arnout

6

Questo semplice esempio ha funzionato per me. Spero che questo ti aiuti.

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

13
Non funzionerà se hai DIREZIONE a = NORD; e poi scrivi ENUM_TO_STR (a)
mathreadler il

5

Hai provato questo:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

La stringify()macro può essere utilizzata per trasformare qualsiasi testo nel codice in una stringa, ma solo il testo esatto tra parentesi. Non ci sono dereferenziazione variabile o sostituzioni di macro o qualsiasi altra cosa fatta.

http://www.cplusplus.com/forum/general/2949/


Questo sarà in realtà il massimo, anche se solo il primo sarebbe sufficiente :)
pholat

Funziona bene, ma dovresti aggiungere #ifndef stringify in alto per evitare errori di compilazione. Ho anche cambiato il tipo di enum come std :: string come suggerito da dgmz.
astarakastara,

5

Ci sono molte buone risposte qui, ma ho pensato che alcune persone potrebbero trovare utili le mie. Mi piace perché l'interfaccia che usi per definire la macro è più semplice che può ottenere. È anche utile perché non è necessario includere ulteriori librerie: viene fornito con C ++ e non richiede nemmeno una versione molto recente. Ho estratto pezzi da vari posti online, quindi non posso prendermi il merito per tutto, ma penso che sia abbastanza unico da giustificare una nuova risposta.

Per prima cosa crea un file di intestazione ... chiamalo EnumMacros.h o qualcosa del genere, e mettici questo:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Quindi, nel tuo programma principale puoi farlo ...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Dove sarebbe l'output >> Il valore di 'Apple' è: 2 di 4

Godere!


La cosa chiave che mi piace di questo particolare approccio è che funziona con la normale sintassi separata dall'enum della virgola (purché non includa assegnazioni di impostazione del valore all'interno dell'enum). Nel mio caso ho dovuto lavorare con un enum esistente con un gran numero di membri, quindi questo è stato molto più facile da inserire rispetto all'approccio boost.
CuriousKea,

4

Supponendo che il tuo enum sia già definito, puoi creare una matrice di coppie:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Ora puoi creare una mappa:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Ora puoi usare la mappa. Se il tuo enum è cambiato, devi aggiungere / rimuovere la coppia dalle coppie di array []. Penso che sia il modo più elegante per ottenere una stringa da enum in C ++.


2
A parte il giusto commento che qui non è necessario Qt, un altro punto è che si potrebbe voler usare Boost bimapnel caso in cui si desideri analizzare i nomi e trasformarli in enum (ad esempio, da un file XML).
Dmitri Nesteruk,

4
Non dovrebbe usare i tipi Qt in una domanda C ++ generica.
Vettore

3

Per C99 c'è P99_DECLARE_ENUMin P99 che ti permette semplicemente di dichiarare enumcosì:

P99_DECLARE_ENUM(color, red, green, blue);

e quindi usare color_getname(A)per ottenere una stringa con il nome del colore.


2

Ecco il mio codice C ++:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

2

Un po 'tardi alla festa, ma ecco la mia soluzione C ++ 11:

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra,

2

La mia preferenza è quella di ridurre al minimo sia la digitazione ripetitiva che le macro difficili da capire ed evitare di introdurre definizioni di macro nello spazio generale del compilatore.

Quindi, nel file di intestazione:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

e l'implementazione di cpp è:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

Nota il #undef della macro non appena abbiamo finito con esso.


2

La mia soluzione, non usando boost:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

Ed ecco come usarlo

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

2

Un altro ritardo alla festa, usando il preprocessore:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(Ho appena inserito i numeri di riga, quindi è più facile parlarne.) Le righe 1-4 sono ciò che modifichi per definire gli elementi dell'enum. (L'ho definita una "macro elenco", perché è una macro che crea un elenco di cose. @Lundin mi informa che si tratta di una tecnica ben nota chiamata X-macros.)

La riga 7 definisce la macro interna in modo da compilare la dichiarazione enum effettiva nelle righe 8-11. La riga 12 definisce la macro interna (solo per mettere a tacere l'avvertimento del compilatore).

La riga 14 definisce la macro interna in modo da creare una versione stringa del nome dell'elemento enum. Quindi le righe 15-18 generano un array che può convertire un valore enum nella stringa corrispondente.

Le righe 21-27 generano una funzione che converte una stringa nel valore enum o restituisce NULL se la stringa non corrisponde a nessuna.

Questo è un po 'ingombrante nel modo in cui gestisce il 0 ° elemento. In realtà ci ho già lavorato in passato.

Ammetto che questa tecnica infastidisce le persone che non vogliono pensare che il preprocessore stesso possa essere programmato per scrivere codice per te. Penso che illustri fortemente la differenza tra leggibilità e manutenibilità . Il codice è difficile da leggere, ma se l'enum ha poche centinaia di elementi, è possibile aggiungere, rimuovere o riorganizzare gli elementi ed essere comunque sicuri che il codice generato non abbia errori.


Le "macro X" raramente rappresentano sempre una soluzione elegante a qualsiasi problema. In questo caso, sarebbe molto più leggibile definire semplicemente gli elementi macro come #define TEST_1 hello #define TEST_2 worldallora typedef enum { TEST_1, TEST_2 } test_t;e quindi creare una tabella di ricerca delle stringhe che utilizza una macro stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Esistono già più risposte che suggeriscono soluzioni simili. Molto più leggibile.
Lundin,

@Lundin: rivendico solo 1) funziona anche con il compilatore C più primitivo, e 2) l'aggiunta o l'eliminazione di un elemento è una modifica di 1 riga.
Mike Dunlavey,

Ho pubblicato una mia risposta: stackoverflow.com/a/39877228/584518 . Spero che salverà qualche povera anima dalle soluzioni di macro x.
Lundin,

1
Ho usato la tua soluzione. Penso che sia il migliore. La sintassi C è ancora lì in modo da capire cosa succede e l'elenco viene definito solo una volta. Puoi rimuovere il 0 ° elemento posizionando la virgola dopo la voce nel tuo DEFINE_ENUM_ELEMENT.
consegnato

1

Ecco il vecchio metodo Skool (usato ampiamente in gcc) usando solo il pre-processore C. Utile se stai generando strutture di dati discrete ma devi mantenere l'ordine coerente tra di loro. Le voci in mylist.tbl possono ovviamente essere estese a qualcosa di molto più complesso.

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

E poi mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

1
Questa tecnica si chiama x macro!
Watusimoto,

0

In c ++ in questo modo:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}

0
#include <EnumString.h>

da http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C e dopo

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

inserire

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Funziona bene se i valori nell'enum non sono duplicati.

Codice di esempio per convertire un valore enum in stringa:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

Codice di esempio per il contrario:

assert( EnumString< FORM >::To( f, str ) );

0

Grazie James per il tuo suggerimento. È stato molto utile, quindi ho implementato il contrario per contribuire in qualche modo.

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data,  elem) \
    case data::elem : return BOOST_PP_STRINGIZE(elem);

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

0

Per estendere la risposta di James, qualcuno vuole un codice di esempio per supportare enum define con valore int, ho anche questo requisito, quindi ecco la mia strada:

La prima è la macro di uso interno, utilizzata da FOR_EACH:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

Ed ecco la macro di definizione:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

Quindi, quando lo usi, potresti scrivere in questo modo:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

che si espanderà a:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

L'idea di base è quella di definire un SEQ, che ogni elemento è una TUPLE, in modo da poter aggiungere valore aggiunto per il membro enum. Nel ciclo FOR_EACH, controlla la dimensione TUPLE dell'articolo, se la dimensione è 2, espandi il codice a KEY = VALUE, altrimenti mantieni solo il primo elemento di TUPLE.

Poiché l'input SEQ è in realtà TUPLE, quindi se si desidera definire le funzioni STRINGIZE, potrebbe essere necessario preelaborare prima gli enumeratori di input, ecco la macro per eseguire il lavoro:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

La macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ manterrà solo il primo elemento in ogni TUPLE, e successivamente convertirà in SEQ, ora modificherai il codice di James, avrai la piena potenza.

La mia implementazione forse non è la più semplice, quindi se non trovi alcun codice pulito, il mio come riferimento.


0

Soluzione pulita e sicura in puro standard C:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

Produzione

0 hello
1 world
1 world

Fondamento logico

Quando si risolve il problema principale "avere costanti enum con stringhe corrispondenti", un programmatore sensibile si presenterà con i seguenti requisiti:

  • Evitare la ripetizione del codice (principio "DRY").
  • Il codice deve essere scalabile, gestibile e sicuro anche se gli articoli vengono aggiunti o rimossi all'interno dell'enum.
  • Tutto il codice dovrebbe essere di alta qualità: facile da leggere, facile da mantenere.

Il primo requisito, e forse anche il secondo, può essere soddisfatto con varie soluzioni macro disordinate come il famigerato trucco "x macro" o altre forme di macro magia. Il problema con tali soluzioni è che ti lasciano con un pasticcio completamente illeggibile di macro misteriose: non soddisfano il terzo requisito di cui sopra.

L'unica cosa necessaria qui è in realtà avere una tabella di ricerca delle stringhe, a cui possiamo accedere usando la variabile enum come indice. Tale tabella deve naturalmente corrispondere direttamente all'enum e viceversa. Quando uno di essi viene aggiornato, anche l'altro deve essere aggiornato, altrimenti non funzionerà.


Spiegazione del codice

Supponiamo di avere un enum simile

typedef enum
{
  hello,
  world
} test_t;

Questo può essere cambiato in

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Con il vantaggio che queste costanti macro possono ora essere utilizzate altrove, ad esempio per generare una tabella di ricerca delle stringhe. La conversione di una costante pre-processore in una stringa può essere eseguita con una macro "stringify":

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

E questo è tutto. Usando hello, otteniamo la costante enum con valore 0. Usandotest_str[hello] otteniamo la stringa "ciao".

Per far corrispondere direttamente l'enum e la tabella di ricerca, dobbiamo assicurarci che contengano la stessa quantità di articoli. Se qualcuno mantiene il codice e modifica solo l'enum, e non la tabella di ricerca, o viceversa, questo metodo non funzionerà.

La soluzione è avere l'enum per dirti quanti elementi contiene. Esiste un trucco C comunemente usato per questo, basta aggiungere un oggetto alla fine, che riempie solo lo scopo di dire quanti oggetti ha l'enum:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Ora possiamo verificare in fase di compilazione che il numero di elementi nell'enum sia pari al numero di elementi nella tabella di consultazione, preferibilmente con un'asserzione statica C11:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(Esistono modi brutti ma completamente funzionali per creare asserzioni statiche anche nelle versioni precedenti dello standard C, se qualcuno insiste sull'uso di compilatori di dinosauri. Per quanto riguarda C ++, supporta anche asserzioni statiche.)


Come nota a margine, in C11 possiamo anche ottenere una maggiore sicurezza del tipo modificando la macro stringify:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( intpoiché le costanti di enumerazione sono in realtà di tipo int, nontest_t )

Ciò impedirà la STRINGIFY(random_stuff)compilazione di codice come .


Capisco quello che stai dicendo, ma il punto rimane. Le tipiche modifiche prevedibili dovrebbero richiedere modifiche minime (come 1 riga). (Penso che sia la ragione dietro DRY.) Quindi qui, se la dimensione dell'enum è come 500, e vuoi inserire un nuovo elemento nel mezzo (o rimuovere / rinominare / scambiare), quante righe di codice devi cambiare e quanto controllo devi fare per essere sicuro di non aver commesso un errore? Potrebbero esserci anche altri pezzi di codice che fanno qualcosa di uniforme per ciascun elemento dell'elenco.
Mike Dunlavey,

Grazie per avermi detto che si chiamano X-macro . Non lo sapevo. Quello che non vedo sono le persone che le denigrano in generale.
Mike Dunlavey,

@MikeDunlavey Indipendentemente dalle dimensioni dell'enum, dovrai cambiare esattamente 3 righe: aggiungi a #define, aggiungi un riferimento a quello definito nella dichiarazione enum e nella tabella di ricerca. Se dovessi alzarti quando aggiungi quelle righe, il programma non verrà compilato. I numeri che ho aggiunto agli identificatori non sono affatto obbligatoria, si potrebbe pure scrivere #define APPLES helloe #define ORANGES worldseguito da typedef enum { APPES, ORANGES, TEST_N } test_t;e così via.
Lundin,

@MikeDunlavey Per quanto riguarda le macro X, gli argomenti contro di loro sono gli stessi degli argomenti contro qualsiasi macro simile a una funzione. Non dovrai guardare lontano per trovare molte valide critiche contro le macro simili a funzioni.
Lundin,

0

Quello che ho fatto è una combinazione di ciò che ho visto qui e in domande simili su questo sito. Ho realizzato questo è Visual Studio 2013. Non l'ho testato con altri compilatori.

Prima di tutto, definisco un insieme di macro che farà i trucchi.

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

Quindi definire una singola macro che creerà la classe enum e le funzioni per ottenere le stringhe.

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

Ora definire un tipo di enum e avere delle stringhe per questo diventa davvero facile. Tutto quello che devi fare è:

ENUM(MyEnumType, A, B, C);

Le seguenti righe possono essere utilizzate per testarlo.

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

Questo produrrà:

3
A
B
C
A
B
C
A
B
C

Credo che sia molto pulito e facile da usare. Ci sono alcune limitazioni:

  • Non è possibile assegnare valori ai membri enum.
  • I valori del membro enum sono usati come indice, ma dovrebbe andare bene, perché tutto è definito in una singola macro.
  • Non puoi usarlo per definire un tipo enum all'interno di una classe.

Se puoi aggirare questo. Penso, soprattutto come usarlo, questo è carino e snello. vantaggi:

  • Facile da usare.
  • Nessuna divisione delle stringhe in fase di esecuzione richiesta.
  • Stringhe separate sono disponibili al momento della compilazione.
  • Facile da leggere. Il primo set di macro potrebbe richiedere un secondo in più, ma non è poi così complicato.

0

Una soluzione pulita a questo problema sarebbe:

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

L'aspetto positivo di questa soluzione è che è semplice e anche la costruzione della funzione può essere eseguita facilmente tramite copia e sostituzione. Nota che se hai intenzione di fare molte conversioni e il tuo enum ha troppi valori possibili, questa soluzione potrebbe richiedere molta CPU.


0

Sono un po 'in ritardo, ma ecco la mia soluzione usando g ++ e solo le librerie standard. Ho cercato di ridurre al minimo l'inquinamento dello spazio dei nomi e rimuovere qualsiasi necessità di riscrivere i nomi enum.

Il file di intestazione "my_enum.hpp" è:

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

Esempio di utilizzo:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

Questo produrrà:

MERCURY
EARTH

Devi definire tutto solo una volta, il tuo spazio dei nomi non dovrebbe essere inquinato e tutto il calcolo viene eseguito una sola volta (il resto sono solo ricerche). Tuttavia, non ottieni la sicurezza del tipo delle classi enum (sono ancora solo numeri brevi), non puoi assegnare valori agli enum, devi definire enum da qualche parte in cui puoi definire spazi dei nomi (ad es. Globalmente).

Non sono sicuro di quanto siano buone le prestazioni su questo, o se sia una buona idea (ho imparato C prima di C ++, quindi il mio cervello funziona ancora così). Se qualcuno sa perché questa è una cattiva idea, sentiti libero di segnalarlo.


0

È il 2017 ma la domanda è ancora viva

Ancora un altro modo:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << NO_ERROR << std::endl;
    std::cout << "Error: " << FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << LABEL_UNINITIALISED << std::endl;
    return 0;
}

Uscite:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage

0
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

Questa è una versione enum estesa della classe elaborata ... non aggiunge altri valori enum diversi da quelli forniti.

Utilizzo: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

Avevo bisogno che funzionasse in entrambe le direzioni E spesso incorporavo le mie enumerazioni in una classe contenitiva, e così ho iniziato con la soluzione di James McNellis, in cima a queste risposte, ma ho fatto questa soluzione. Nota anche che preferisco enum class piuttosto che solo enum, il che complica in qualche modo la risposta.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

Per usarlo all'interno di una classe, potresti fare qualcosa del genere:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

E ho scritto un test CppUnit, che dimostra come usarlo:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

Devi scegliere quale macro utilizzare, DEFINE_ENUMERATION o DEFINE_ENUMERATION_INSIDE_CLASS. Vedrai che ho usato quest'ultimo per definire ComponentStatus :: Status, ma ho usato il primo per definire Status. La differenza è semplice All'interno di una classe, prefisso i metodi to / from come "statici" e, se non in una classe, utilizzo "inline". Differenze fondamentali, ma necessarie.

Sfortunatamente, non penso che ci sia un modo pulito per evitare di doverlo fare:

const char * valueStr = ComponentStatus::ToString(value);

sebbene sia possibile creare manualmente un metodo inline dopo la definizione della classe che si collega semplicemente al metodo di classe, qualcosa del tipo:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

0

La mia risposta, non usando il boost - usando il mio approccio senza pesante definizione di magia, e questa soluzione ha un limite di non essere in grado di definire il valore enum specifico.

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

L'ultima versione può essere trovata su github qui:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h


0

Ci sono molte altre risposte a questo, ma penso che un modo migliore sia usare le funzionalità di C ++ 17 e usare constexpr in modo che le traduzioni vengano fatte in fase di compilazione. Questo è sicuro e non è necessario fare confusione con le macro. Vedi sotto:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

Questo può quindi essere facilmente utilizzato in modo che gli errori della chiave stringa vengano rilevati al momento della compilazione:

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

Il codice è più dettagliato di alcune altre soluzioni, ma possiamo facilmente eseguire la conversione da Enum a String e la conversione da String a Enum al momento della compilazione e rilevare errori di tipo. Con alcune delle future funzionalità di C ++ 20 questo può probabilmente essere semplificato un po 'di più.

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.