enum to string nel moderno C ++ 11 / C ++ 14 / C ++ 17 e futuro C ++ 20


354

Contrariamente a tutte le altre domande simili, questa domanda riguarda l'uso delle nuove funzionalità C ++.

Dopo aver letto molte risposte, non ne ho ancora trovato:

  • Modo elegante utilizzando le nuove funzionalità di C ++ 11 , C ++ 14 o C ++ 17
  • O qualcosa di pronto per l'uso in Boost
  • Altrimenti qualcosa pianificato per C ++ 20

Esempio

Un esempio è spesso meglio di una lunga spiegazione.
Puoi compilare ed eseguire questo frammento su Coliru .
( È disponibile anche un altro esempio precedente )

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

vincoli

  • Per favore, nessuna duplicazione senza valore di altre risposte o link di base .
  • Si prega di evitare una risposta basata su macro gonfia o provare a ridurre il #definesovraccarico il meno possibile.
  • Per favore, nessun manuale enum-> stringmapping.

Bello avere

  • Supporta enumvalori a partire da un numero diverso da zero
  • Supporta enumvalori negativi
  • Supporta enumvalori frammentati
  • Supporto class enum(C ++ 11)
  • Supporto class enum : <type>con qualsiasi permesso <type>(C ++ 11)
  • Conversioni in fase di compilazione (non di runtime) in una stringa,
    o almeno esecuzione veloce in fase di runtime (ad esempio, std::mapnon è una grande idea ...)
  • constexpr (C ++ 11, quindi rilassato in C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Snippet amichevole C ++ 17 / C ++ 20

Una possibile idea potrebbe essere quella di utilizzare le funzionalità del compilatore C ++ per generare codice C ++ in fase di compilazione utilizzando trucchi di meta-programmazione basati su variadic template classe constexprfunzioni ...


4
(forse argomento) guarda questo blog relativo a Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Descrive la possibilità di sostituire il moc di Qt (meta-oggetto-compilatore) usando la riflessione C ++ (standard proposto).
ibre5041,

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
Personalmente ho risolto questo problema implementando una piccola libreria di utilità del preprocessore che mi consente di eseguire il ciclo su argomenti macro variadici ed eseguire una funzione su tutti. Passo i valori di enum come argomenti macro e genera automaticamente enum e array di stringhe attraverso il preprocessore. Probabilmente puoi farlo anche usando Boost Preprocessor.
Vittorio Romeo

2
tutto deve essere risolto con C ++? È così facile generare automaticamente il codice per la rappresentazione di stringhe, solo un paio di righe di codice.
Karoly Horvath,

2
"Se possibile, non fornire risposte basate su macro C", a meno che tu non sia disposto ad aspettare C ++ 17, non c'è quasi nulla di utilizzabile, e non è poi così grave dichiarare i tuoi enum come DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))se non devi mantenere la generare macro ... e imho inserendo questi casi nel linguaggio è solo un altro tipo di hack al posto di un ibrido template / macro più potente. Non dovremmo aggiungere tutte quelle utili userecase di macro nella lingua solo per poter dire che le macro non servono più.
PlasmaHH,

Risposte:


43

La libreria di sola intestazione di Magic Enum fornisce una riflessione statica per gli enum (a stringa, da stringa, iterazione) per C ++ 17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Per ulteriori esempi, consultare il repository home https://github.com/Neargye/magic_enum .

Dov'è lo svantaggio?

Questa libreria utilizza un hack specifico del compilatore (basato su __PRETTY_FUNCTION__/ __FUNCSIG__), che funziona su Clang> = 5, MSVC> = 15.3 e GCC> = 9.

Il valore enum deve essere compreso nell'intervallo [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Per impostazione predefinita MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Se per impostazione predefinita è necessario un altro intervallo per tutti i tipi di enum, ridefinire la macro MAGIC_ENUM_RANGE_MINe MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINdeve essere minore o uguale a 0e deve essere maggiore di INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXdeve essere maggiore di 0e deve essere minore di INT16_MAX.

  • Se è necessario un altro intervallo per un tipo di enum specifico, aggiungere la specializzazione enum_range per il tipo di enum necessario.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

Perché i limiti di portata? Limitare un qualche tipo di profondità di ricorsione o a causa di un qualche tipo di ricerca lineare in fase di compilazione?
Emile Cormier,

Questo è fantastico Grazie! Probabilmente è persino efficace se il compilatore è abbastanza intelligente da valutare una sola volta constexpr std :: array. Molto molto carino.
iestyn,

87

(L'approccio della libreria better_enums )

C'è un modo per enumare la stringa nel C ++ corrente che assomiglia a questo:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Uso:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Tutte le operazioni possono essere fatte constexpr. Puoi anche implementare la proposta di riflessione C ++ 17 menzionata nella risposta di @ecatmur.

  • C'è solo una macro. Credo che questo sia il minimo possibile, perché la stringa di preprocessore ( #) è l'unico modo per convertire un token in una stringa nel C ++ corrente.
  • La macro è piuttosto discreta: le dichiarazioni costanti, inclusi gli inizializzatori, vengono incollate in una dichiarazione enum incorporata. Ciò significa che hanno la stessa sintassi e significato di un enum incorporato.
  • La ripetizione è eliminata.
  • L'implementazione è molto naturale e utile almeno in C ++ 11, a causa di constexpr. Può anche essere fatto funzionare con C ++ 98 + __VA_ARGS__. È sicuramente un C ++ moderno.

La definizione della macro è in qualche modo coinvolta, quindi sto rispondendo in diversi modi.

  • La maggior parte di questa risposta è un'implementazione che ritengo adatta ai vincoli di spazio su StackOverflow.
  • C'è anche un articolo di CodeProject che descrive le basi dell'implementazione in un tutorial di lunga durata. [ Dovrei spostarlo qui? Penso che sia troppo per una risposta SO ].
  • Esiste una libreria con funzionalità complete "Better Enums" che implementa la macro in un singolo file di intestazione. Implementa anche le query sulle proprietà del tipo N4428 , l'attuale revisione della proposta di riflessione C ++ 17 N4113. Quindi, almeno per gli enum dichiarati attraverso questa macro, puoi avere ora la riflessione enum C ++ 17 proposta, in C ++ 11 / C ++ 14.

È semplice estendere questa risposta alle caratteristiche della biblioteca - qui non è lasciato fuori nulla di "importante". È, tuttavia, abbastanza noioso e ci sono problemi di portabilità del compilatore.

Disclaimer : sono l'autore sia dell'articolo CodeProject che della biblioteca.

Puoi provare il codice in questa risposta , la libreria e l'implementazione di N4428 in diretta online su Wandbox. La documentazione della biblioteca contiene anche una panoramica su come usarlo come N4428 , che spiega la parte relativa agli enumeratori di quella proposta.


Spiegazione

Il codice seguente implementa le conversioni tra enum e stringhe. Tuttavia, può essere esteso anche per fare altre cose, come l'iterazione. Questa risposta racchiude un enum in a struct. Puoi anche generare un tratto structinsieme a un enum invece.

La strategia è generare qualcosa del genere:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

I problemi sono:

  1. Finiremo con qualcosa come {Red = 1, Green, Blue}l'inizializzatore per l'array dei valori. Questo non è un C ++ valido, perché Rednon è un'espressione assegnabile. Ciò è risolto da colata ogni costante a un tipo Tche ha un operatore di assegnazione, ma scenderà cessione: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Allo stesso modo, finiremo con {"Red = 1", "Green", "Blue"}come inizializzatore per l'array dei nomi. Dovremo tagliare il " = 1". Non sono a conoscenza di un ottimo modo per farlo al momento della compilazione, quindi lo rimanderemo al tempo di esecuzione. Di conseguenza, _to_stringnon lo sarà constexpr, ma _from_stringpuò ancora esserlo constexpr, poiché possiamo confrontare gli spazi bianchi ed eguagliare i segni come terminatori quando confrontiamo con stringhe non tagliate.
  3. Entrambi i precedenti hanno bisogno di una macro di "mappatura" in grado di applicare un'altra macro a ciascun elemento in __VA_ARGS__. Questo è piuttosto standard. Questa risposta include una versione semplice che può gestire fino a 8 elementi.
  4. Se la macro deve essere veramente autonoma, non deve dichiarare dati statici che richiedono una definizione separata. In pratica, ciò significa che le matrici richiedono un trattamento speciale. Esistono due possibili soluzioni: constexpr(o solo const) matrici nell'ambito dello spazio dei nomi o matrici regolari in constexprfunzioni inline non statiche. Il codice in questa risposta è per C ++ 11 e utilizza il primo approccio. L'articolo di CodeProject è per C ++ 98 e prende quest'ultimo.

Codice

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

e

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

Il programma qui sopra viene stampato Red, come ci si aspetterebbe. Esiste un certo grado di sicurezza dei tipi, poiché non è possibile creare un enum senza inizializzarlo e l'eliminazione di uno dei casi dal switchcomporterà un avviso dal compilatore (a seconda del compilatore e dei flag). Si noti inoltre che "Red"durante la compilazione è stato convertito in enum.


Heya @mrhthepie, mi dispiace che la tua modifica sia stata respinta. Ho appena visto l'email a riguardo. Ho intenzione di incorporarlo nella risposta - grazie per la correzione!
Antron,

è fantastico Funzionerebbe anche se desidero un enum di bit? Come se volessi un enum di BitFlags, ognuno viene 1Uspostato di una certa quantità?
user3240688

1
sembra che ci sia una perdita di memoria nel _trimmed_names()codice che hai pubblicato qui ( new char[length + 1]ma non imposti initializedsu true). mi sto perdendo qualcosa? non vedo lo stesso problema nel tuo codice github.
user3240688,

1
È impostato su true, ma all'esterno del iframo (perdita di memoria originariamente rilevata da @mrhthepie). Dovrebbe spostarlo all'interno ... Modifica. Grazie per lo sguardo ravvicinato sia a questo che al codice GH.
Antron,

1
to_stringpotrebbe restituire a string_viewda C ++ 17, che non richiede una terminazione nulla, e diventare constexpr.
Yakk - Adam Nevraumont,

74

Per C ++ 17 C ++ 20, sarai interessato al lavoro del Reflection Study Group (SG7). Esiste una serie parallela di articoli riguardanti la formulazione ( P0194 ) e la logica, il design e l'evoluzione ( P0385 ). (I collegamenti si risolvono all'ultimo documento di ogni serie.)

A partire da P0194r2 (15-10-2016), la sintassi utilizza la reflexprparola chiave proposta :

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Ad esempio (adattato dal ramo riflesso di Clang di Matus Choclik ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

La riflessione statica non è riuscita a farcela in C ++ 17 (piuttosto, nella bozza probabilmente finale presentata all'incontro degli standard di novembre 2016 a Issaquah) ma c'è la sicurezza che lo farà diventare C ++ 20; dal rapporto di viaggio di Herb Sutter :

In particolare, il gruppo di studio Reflection ha esaminato l'ultima proposta di riflessione statica unita e l'ha trovata pronta a entrare nei principali gruppi Evolution nel nostro prossimo incontro per iniziare a considerare la proposta di riflessione statica unificata per un TS o per lo standard successivo.


2
@antron mi dispiace che la tua modifica sia stata respinta; Lo avrei approvato se l'avessi visto in tempo. Non avevo visto N4428, quindi grazie per avermi dato la testa.
ecatmur,

3
Nessun problema, grazie per averlo incorporato. Mi chiedo perché sia ​​stato respinto. Vedo il motivo "non lo rende più accurato", ma è chiaramente più accurato per i giorni nostri.
Antron,

1
Grazie :-) Ho diviso l'esempio finale per evitare la barra di scorrimento orizzontale. Peccato che il valore MyEnum::AAAnon possa essere passato come secondo argomento di std::meta::get_enumerators_m: - /
olibre

1
Il fatto che un'attività così concettualmente semplice richieda 3 livelli di argomenti modello nidificati è molto ingegnerizzata. Sono sicuro che ci sono ragioni tecniche specifiche. Ciò non significa che il risultato finale sia facile da usare. Adoro il C ++ e il codice ha senso per me. Ma il 90% degli altri programmatori con cui lavoro quotidianamente evita C ++ a causa di questo codice. Sono deluso dal non aver visto soluzioni più semplici e integrate.
void.pointer

2
Sembra che la stima attuale per l'inclusione del prossimo Reflection TS nella norma sia C ++ 23 : herbsutter.com/2018/04/02/…
Tim Rae

25

Questo è simile a Yuri Finkelstein; ma non richiede boost. Sto usando una mappa in modo da poter assegnare qualsiasi valore agli enum, a qualsiasi ordine.

Dichiarazione della classe enum come:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

Il codice seguente creerà automaticamente la classe enum e il sovraccarico:

  • '+' '+ =' per std :: string
  • '<<' per i flussi
  • '~' solo per convertire in stringa (qualsiasi operatore unario lo farà, ma personalmente non mi piace per chiarezza)
  • '*' per ottenere il conteggio delle enumerazioni

Nessuna spinta richiesta, tutte le funzioni richieste fornite.

Codice:

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

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Esempio:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
Possiamo avere interruzioni di riga all'interno di questa definizione macro?
einpoklum,

1
Ho aggiunto il sovraccarico per *ottenere il conteggio degli enumerati ... Spero non ti dispiaccia :-)
Peter VARGA

1
C'è qualche motivo per cui questa implementazione utilizza std::map(O (log (n)) indicizzazione) piuttosto che std::unordered_map(O (1) indicizzazione)?
River Tam,

1
inoltre, penso che i metodi dovrebbero essere contrassegnati in inlinemodo da poter dichiarare enum nei file di intestazione come normali senza ottenere errori di "definizione multipla" dal linker. (non sono sicuro che sia la soluzione più pulita / migliore, però)
River Tam

1
(mi dispiace per lo spam ma non riesco a modificare i commenti oggi) ci sono altri problemi con questo essere in un file di intestazione. La mappa ( E##MapName) deve essere spostata in un'unità di compilazione che ha accesso anche all'enum. Ho creato una soluzione, ma non è molto pulita e dovrei ottenere l'autorizzazione per condividerla. Per ora, sto solo commentando per dire che non ha senso contrassegnare i metodi in linea senza le funzionalità aggiuntive necessarie per supportare l'utilizzo in un file di intestazione.
River Tam,

19

Nel 2011 ho trascorso un fine settimana a perfezionare una soluzione basata su macro e alla fine non l'ho mai utilizzata.

La mia procedura attuale è di avviare Vim, copiare gli enumeratori in un corpo di switch vuoto, avviare una nuova macro, trasformare il primo enumeratore in un'istruzione case, spostare il cursore all'inizio della riga successiva, interrompere la macro e generare il caso rimanente istruzioni eseguendo la macro sugli altri enumeratori.

Le macro Vim sono più divertenti delle macro C ++.

Esempio di vita reale:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Creerò questo:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

Ed è così che riesco a cavarmela.

Il supporto nativo per la stringificazione dell'enum sarebbe molto meglio però. Sono molto interessato a vedere i risultati del gruppo di lavoro di riflessione in C ++ 17.

Un modo alternativo per farlo è stato pubblicato da @sehe nei commenti .


1
Faccio esattamente questo. Anche se di solito uso Surround vim e blocco le selezioni lungo la strada
vedi l'

@sehe Interessante. Dovrei dare un'occhiata a "surround" perché attualmente ho bisogno di molti modi per premere i tasti.
StackedCrooked

Eccolo in pieno gory, niente macro (se non .conta): i.imgur.com/gY4ZhBE.gif
vedi l'

1
La gif animata è carina, ma è difficile dire quando inizia e finisce e quanto siamo lontani. ... effettivamente, grattalo, non è carino, è fonte di distrazione. Dico uccidilo.
einpoklum,

Questo approccio di selezione dei blocchi in vim è bello e tutto, ma perché non usare semplicemente qualcosa del genere :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan,

14

Non so se ti piacerà o no, non sono abbastanza contento di questa soluzione, ma è un approccio amichevole in C ++ 14 perché utilizza le variabili dei modelli e abusa della specializzazione dei modelli:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Il peggio di questo approccio è che è un dolore da mantenere, ma è anche un dolore mantenere alcuni di altri simili approcci, non è vero?

Aspetti positivi di questo approccio:

  • Uso di tempates variabili (funzionalità C ++ 14)
  • Con la specializzazione dei modelli possiamo "rilevare" quando viene utilizzato un valore non valido (ma non sono sicuro che ciò possa essere utile).
  • Sembra pulito.
  • La ricerca del nome viene eseguita al momento della compilazione.

Live example

modificare

Misterioso user673679 hai ragione; l'approccio del modello variabile C ++ 14 non gestisce il caso di runtime, è stata colpa mia se l'ho dimenticato :(

Ma possiamo ancora usare alcune moderne funzionalità C ++ e template variabili oltre a un trucco variadic template per ottenere una traduzione runtime dal valore enum a string ... è fastidioso come gli altri ma vale comunque la pena menzionare.

Iniziamo a utilizzare un alias modello per abbreviare l'accesso a una mappa enum-to-string:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Quindi, l'inganno del modello variadico:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

Il " miglior trucco " qui è l'uso del modello variabile per la mappa che contiene i valori e i nomi di ciascuna voce enum; questa mappa sarà la stessa in ogni unità di traduzione e avrà lo stesso nome ovunque, quindi è piuttosto semplice e pulita, se chiamiamo la initializefunzione in questo modo:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Stiamo assegnando nomi a ciascuna MyEnumvoce e possiamo usarli in fase di esecuzione:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Ma può essere migliorato con SFINAE e l' <<operatore di sovraccarico :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

Con il corretto operator <<ora possiamo usare l'enum in questo modo:

std::cout << MyEnum::AAA << '\n';

Anche questo è fastidioso da mantenere e può essere migliorato, ma spero che tu abbia l'idea.

Live example


Sembra abbastanza pulito (è possibile semplicemente non definire la variabile non specializzata?). Forse mi manca qualcosa, anche se non vedo come gestisce il caso di runtime.
user673679

@Paula_plus_plus: non dovresti usare solo una std::arraymappa ingombrante? Diventerà preferibile solo per gli enum a partire da ... cosa, 2 ^ 10 valori? Forse anche di più.
einpoklum,

@einpoklum sarebbe fantastico se potessimo garantire in fase di esecuzione quanti elementi ci enumsono. Sfortunatamente, non possiamo. E il punto centrale della mappa è solo quello di associare i nomi ai valori, il che std::mapè utile.
PaperBirdMaster l'

@Paula_plus_plus: stai già chiamando una initialize()funzione il cui numero di argomenti è il numero di valori enum, quindi conosci il numero di valori in fase di compilazione. È solo il valore specifico che ti viene chiesto di stampare che è noto solo in fase di esecuzione. Inoltre, anche se non conoscessi quel numero, uno std :: vector sarebbe più veloce di uno std :: map, ancora una volta, in quasi tutti i casi realistici.
einpoklum,

@einpoklum questo è davvero un ottimo punto, ci penserò, grazie! L'unica cosa che mi preoccupa è che std::arraynon è un contenitore di valori-chiave e quindi manca di metodi di ricerca; comunque ci penserò.
PaperBirdMaster l'

7

Se il tuo enumaspetto è simile

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

È possibile spostare il contenuto di enumin un nuovo file:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

E quindi i valori possono essere circondati da una macro:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

Il prossimo passo potrebbe essere includere di nuovo gli elementi nel enum:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

E infine puoi generare funzioni di utilità a riguardo enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

La soluzione può essere applicata a standard C ++ precedenti e non utilizza elementi C ++ moderni, ma può essere utilizzata per generare molto codice senza troppi sforzi e manutenzione.


3
Non è necessario un file separato. Questa è essenzialmente una x-macro .
HolyBlackCat

@HolyBlackCat se dividi la soluzione in alcuni file puoi riutilizzare i valori enum per scopi diversi
eferione

Sto provando a dirti che puoi fare la stessa cosa se metti la lista dei valori in una singola macro insieme alla definizione enum in un'intestazione.
HolyBlackCat

@HolyBlackCat sì, ti capisco ma preferisco questa soluzione. d'altra parte questa soluzione può essere trovata nel codice sorgente di clang, quindi penso che sia un buon modo per risolvere il problema
eferione

Giusto. Non avrei dovuto sottovalutare questo, immagino, dal momento che può effettivamente avere alcuni usi. (Perdonate il manichino modifica, altrimenti il ​​sistema blocca il mio voto.)
HolyBlackCat

6

Ho avuto lo stesso problema un paio di giorni fa. Non sono riuscito a trovare alcuna soluzione C ++ senza qualche strana magia della macro, quindi ho deciso di scrivere un generatore di codice CMake per generare semplici istruzioni di caso switch.

Uso:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

La funzione cerca i file include nel filesystem (usa le directory include fornite con il comando include_directories), le legge e fa qualche regex per generare la classe e le funzioni.

NOTA: constexpr implica inline in C ++, quindi l'uso dell'opzione USE_CONSTEXPR genererà una classe solo header!

Esempio:

./includes/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

genera:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Aggiornare:

Lo script ora supporta anche le enumerazioni con ambito (classe enum | struct) e l'ho spostato in un repository separato con alcuni altri script che uso spesso: https://github.com/mensinda/cmakeBuildTools


Wow! Idea molto originale e innovativa :-) Spero che tu abbia il coraggio di aggiornare il tuo generatore per fornire una versione constexpre noexcept;-) Ho anche appena fissato il tuo progetto GitHub ;-) Saluti
olibre

1
Aggiornato il generatore. Le funzioni ora saranno sempre constexpr ed enum: <type> è ora supportato. Grazie per la stella :)
Mense

Il collegamento è interrotto ... -.-
yeoman il

Il collegamento è ora risolto.
Mense

4

Genera solo le tue enumerazioni. Scrivere un generatore a tale scopo richiede circa cinque minuti di lavoro.

Codice generatore in java e python, semplicissimo da portare in qualsiasi lingua ti piaccia, incluso C ++.

Anche super facile da estendere con qualsiasi funzionalità tu voglia.

esempio di input:

First = 5
Second
Third = 7
Fourth
Fifth=11

intestazione generata:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

file cpp generato

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

E il generatore, in una forma molto concisa come modello per il porting e l'estensione. Questo codice di esempio cerca davvero di evitare di sovrascrivere qualsiasi file ma di utilizzarlo a proprio rischio.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

E una porta per Python 3.5 perché abbastanza diversa da essere potenzialmente utile

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

1
Grazie mille per aver condiviso il tuo generatore in due lingue :-) Ma hai idea di come generare in fase di compilazione? Ad esempio, possiamo immaginare di tradurre il tuo generatore usando le istruzioni CMake per aggiornare il codice generato C ++ quando i dati di input vengono modificati? Il mio sogno è di forzare il compilatore C ++ a generare enumerazioni durante la compilazione usando la meta-programmazione ( variadic template classe le constexprfunzioni).
olibre

Otoh, nel caso in cui sia troppo ingombrante per aggiungere un comando cmake personalizzato, puoi automatizzare il tuo IDE o chiamare manualmente il gereratore e avere l'output nel controllo del codice sorgente. A volte è una buona idea avere generato codice nel controllo del codice sorgente, purché non sia troppo e le persone capiscono che non dovrebbero apportare modifiche manuali, perché a volte è interessante guardare la cronologia dei file generati quando si stai eseguendo il debug di qualcosa di strano e hai il sospetto che una recente modifica al generatore possa aver rotto qualcosa :)
yeoman

Informazioni sulla generazione di cose in fase di compilazione, è molto semplice in LISP perché la sintassi è estremamente semplice e pulita. Ciò è aiutato dal fatto che è tipizzato in modo dinamico, il che gli consente di essere conciso e leggibile senza molta sintassi. L'equivalente delle macro LISP in C ++ avrebbe bisogno di un modo molto complicato per descrivere l'AST di ciò che stai cercando di generare. E un AST per C ++ non è mai carino :(
yeoman

Direttamente in Make invece di cmake, è super facile tra l'altro. È sufficiente generare target .h e .cpp per ciascun file .enum tramite find e fare in modo che questi target dipendano dai suddetti valori enum, quindi vengono rigenerati automaticamente una volta cambiati i file .enum def. Probabilmente è molto più facile in cmake perché è pieno di magia per questo tipo di cose ma uso regolarmente Make, formica e gradle, ma ho solo una conoscenza limitata di Maven, cmake e grugnito :)
yeoman

Grazie per la tua risposta :-) Penso che la maggior parte degli sviluppatori C ++ apprezzerà il fatto che il tuo generatore sia in grado di rilevare enumerazioni direttamente all'interno del codice C ++ come enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};o in più righe :-D Pensi di poter adattare il tuo generatore per rilevare un enumall'interno di un C ++ file? Il migliore potrebbe essere quello di generare codice solo al rilevamento di un tag simile /*<Generate enum to string here>*/. Quindi il tuo generatore scrive sul posto il corrispondente codice generato C ++ (sostituendo il precedente codice generato). ^ _ ^ Che generatore fantastico non è? Saluti :-)
olibre

3

Come da richiesta del PO, ecco una versione ridotta della brutta soluzione macro basata su Boost Preprosessor e Variadic Macro .

Permette un semplice elenco come la sintassi degli elementi dell'enumeratore insieme all'impostazione dei valori per elementi specifici in modo che

XXX_ENUM(foo,(a,b,(c,42)));

si espande a

enum foo {
    a,
    b,
    c=42
};

Oltre alle funzioni necessarie per l'output e la conversione indietro. Questa macro è in circolazione da secoli, e non sono del tutto sicuro che sia il modo più efficiente o che sia un modo conforme, ma da allora ha funzionato

Il codice completo può essere visto in azione su Ideone e Coliru .

La sua bruttezza gigantesca è al di sopra; Lo avrei messo dietro gli spoiler per proteggere i tuoi occhi, se avessi saputo, ma il markdown non mi piace.

La libreria (unita in un singolo file di intestazione)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         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,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

uso

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Compilazione (copia incolla intestazione all'interno main.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Produzione

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

5
Questo blocco di codice è un folle viaggio attraverso gli incredibili paesaggi della metaprogrammazione della magia nera. In realtà mi sono sentito sollevato quando ho raggiunto main- Casa, dolce casa!
Quentin,

Ho appena aggiunto un link a coliru per controllare l'output (ci sono alcuni avvisi, fai clic sul link nella tua risposta). Ho anche diviso in Lib / Usage. Le cose namespace xxxpossono essere spostate nella posizione dell'intestazione? Puoi dire nell'introduzione il tuo uso boost/preprocessor.hppe quindi la risposta è moderna C ++ conforme . Correggi gli avvisi e pulisci un po 'il codice sorgente per una migliore qualità.
olibre

@olibre: è copypastad da credo 5 diverse intestazioni nella nostra libreria. Enum_cast proviene da un'altra parte più generale, ma ho pensato di aggiungerlo anche per vedere a cosa serve il do_enum_cast nella macro main<tab>. Non penso che questo codice possa essere veramente ripulito, è solo per mostrare cosa si può fare e non dovrebbe essere;) e se lo cambio qui non è più il codice che uso in produzione ... è una di quelle cose fragili che una volta che funziona è meglio non toccarlo mai poiché potrebbe collassare in modi che nessuno poteva prevedere.
PlasmaHH,

Va bene Plasma, vedo che questo può essere visto come una prova di concetto . Ma c'è troppo sovraccarico macro per essere votato. Tuttavia grazie per la condivisione. Saluti
olibre

Ciao al plasma. Ho eseguito una pulizia profonda del codice sorgente + completata dalla compilazione ed eseguo l'output. Si prega di controllare la mia modifica . Spero che questo vada bene per te. La risposta è più preziosa? Tuttavia, il sovraccarico macro è ancora orribile! Buona giornata :-) Saluti
olibre

2

La seguente soluzione si basa su a std::array<std::string,N>per un dato enum.

Per enumdi std::stringconversione possiamo semplicemente lanciare l'enumerazione di size_te ricercare il filo dalla matrice. L'operazione è O (1) e non richiede allocazione di heap.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

Per std::stringper enumla conversione ci sarebbe da fare una ricerca lineare sopra la matrice e gettato l'indice array enum.

Provalo qui con esempi di utilizzo: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Modifica: rielaborata la mia soluzione in modo che l'Enum personalizzato potesse essere utilizzato all'interno di una classe.


Grazie per la tua risposta interessante. Rielaborare la proposta per utilizzare la macro all'interno di una classe. Vedi coliru.stacked-crooked.com/a/00d362eba836d04b Inoltre cerca di usare constexpre noexeptparole chiave quando possibile. Saluti :-)
olibre

La domanda non specifica questo requisito.
FKaria,

Domanda aggiornata (vedi esempio). Altri due requisiti: (1) tipo di supporto di enum e (2) i valori possono essere diversi dalla sequenza 0, 1, 2 ...
olibre

Ho rielaborato la mia soluzione per usarla all'interno di una classe. Non ho capito come rendere i valori diversi da 0,1,2, .. comunque.
FKaria,

Ciao FKaria. Grazie mille per le tue rielaborazioni. Ho apportato alcune modifiche per supportare diversi enum all'interno della stessa classe e anche per supportare il enum class X : Typeformato. Si prega di rivedere il mio contributo: coliru.stacked-crooked.com/a/b02db9190d3491a3 Cosa ne pensate delle mie modifiche? Hai qualche idea per supportare i valori impostati in enum? Esempio enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Questa sintesi fornisce una semplice mappatura basata su modelli variadic C ++.

Questa è una versione semplificata in C ++ 17 della mappa basata sul tipo dal gist :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

Un esempio di utilizzo:

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

La map<KeyValues...>può essere utilizzato in entrambe le direzioni:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Questo esempio è disponibile su godbolt.org

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

Risultato da gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Modo meta-programmazione molto interessante. Ho cercato di semplificare un po 'la risposta in modo autonomo (senza dipendenza dal collegamento Gist). Per essere conciso e comprensibile ho finalmente modificato molto la tua risposta. Sei ancora d'accordo con le mie modifiche? Saluti ;-)
olibre

2

Sono stato frustrato da questo problema anche per molto tempo, insieme al problema di ottenere un tipo convertito in stringa in modo corretto. Tuttavia, per l'ultimo problema, sono rimasto sorpreso dalla soluzione spiegata in È possibile stampare il tipo di una variabile in C ++ standard? , usando l'idea di Posso ottenere i nomi di tipo C ++ in modo constexpr? . Usando questa tecnica, una funzione analoga può essere costruita per ottenere un valore enum come stringa:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

Il codice sopra è stato testato solo su Clang (vedi https://ideone.com/je5Quv ) e VS2015, ma dovrebbe essere adattabile ad altri compilatori giocherellando un po 'con le costanti intere. Ovviamente, usa ancora macro sotto il cofano, ma almeno uno non ha bisogno di accedere all'implementazione enum.


Questo non riesce con g ++ 6.3.0 e C ++ 14.
einpoklum,

Interessante perché l'enum può essere dichiarato normalmente senza doverlo avvolgere in una macro. Anche se non mi piacciono le dipendenze del compilatore e le costanti magiche.
zett42,

2

Ho preso l'idea da @antron e l'ho implementata diversamente: generare una vera classe enum .

Questa implementazione soddisfa tutti i requisiti elencati nella domanda originale ma attualmente ha solo una vera limitazione : presume che i valori enum non siano forniti o, se forniti, devono iniziare con 0 e salire in sequenza senza lacune.

Questa non è una limitazione intrinseca - semplicemente che non uso valori enum ad hoc. Se necessario, è possibile sostituire la ricerca vettoriale con l'implementazione tradizionale di switch / case.

La soluzione utilizza un po 'di c ++ 17 per le variabili inline ma questo può essere facilmente evitato se necessario. Utilizza anche boost: trim per semplicità.

Ancora più importante, ci vogliono solo 30 righe di codice e nessuna macro di magia nera. Il codice è sotto. È pensato per essere messo nell'intestazione e incluso in più moduli di compilazione.

Può essere usato nello stesso modo suggerito in precedenza in questo thread:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Fammi sapere se questo è utile e come può essere ulteriormente migliorato.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

Finché non si va bene con la scrittura di una .h/.cppcoppia separata per ogni enum interrogabile, questa soluzione funziona quasi con la stessa sintassi e capacità di un normale enum c ++:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

Il .cppfile è composto da 3 righe di boilerplate:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Esempio di utilizzo:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Codice

Questa soluzione richiede 2 file di origine:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...e

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

Spiegazione

Questa implementazione sfrutta il fatto che l'elenco rinforzato di elementi di una definizione enum può essere utilizzato anche come elenco di inizializzazione rinforzato per l'inizializzazione dei membri della classe.

Quando ETRAITSviene valutato nel contesto di EnumTraits.inl, si espande in una definizione di membro statico per la EnumTraits<>classe.

La EDECLmacro trasforma ciascun membro enum in valori di elenco di inizializzatori che successivamente vengono passati al costruttore del membro per popolare le informazioni enum.

La EnumInitGuardclasse è progettata per consumare i valori dell'inizializzatore enum e quindi comprimersi, lasciando un puro elenco di dati enum.

Benefici

  • c++-come sintassi
  • Funziona in modo identico per entrambi enume enum class(* quasi)
  • Funziona enumcon tipi con qualsiasi tipo numerico sottostante
  • Funziona enumcon tipi con valori di inizializzazione automatici, espliciti e frammentati
  • Opere per rinominare in massa (preservare il collegamento intellisense)
  • Solo 5 simboli di preprocessore (3 globali)

* Al contrario enums, gli inizializzatori in enum classtipi che fanno riferimento ad altri valori dello stesso enum devono avere quei valori pienamente qualificati

svantaggi

  • Richiede una .h/.cppcoppia separata per ogni queryenum
  • Dipende da contorto macroe includemagia
  • Piccoli errori di sintassi esplodono in errori molto più grandi
  • La definizione classo la definizione di namespaceenumerazioni non è banale
  • Nessuna inizializzazione del tempo di compilazione

Commenti

Intellisense si lamenterà un po 'dell'accesso dei membri privati ​​al momento dell'apertura EnumTraits.inl, ma poiché le macro espanse in realtà definiscono i membri della classe, questo non è in realtà un problema.

Il #ifndef ENUM_INCLUDE_MULTIblocco nella parte superiore del file di intestazione è un fastidio minore che potrebbe probabilmente essere ridotto in una macro o qualcosa del genere, ma è abbastanza piccolo da convivere con le sue dimensioni attuali.

La dichiarazione di un enum con ambito namespace richiede che l'enum sia prima dichiarato in avanti all'interno del suo ambito namespace, quindi definito nel namespace globale. Inoltre, qualsiasi inizializzatore enum che utilizza valori dello stesso enum deve avere tali valori pienamente qualificati.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

Non sono sicuro che questo approccio sia già trattato in una delle altre risposte (in realtà lo è, vedi sotto). Ho riscontrato il problema molte volte e non ho trovato una soluzione che non usasse macro offuscate o librerie di terze parti. Quindi ho deciso di scrivere la mia versione macro offuscata.

Quello che voglio abilitare è l'equivalente di

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

che dovrebbe stampare

ONE
TWO
13

Non sono un fan delle macro. Tuttavia, a meno che c ++ supporti nativamente la conversione di enum in stringhe, è necessario utilizzare una sorta di generazione di codice e / o macro (e dubito che ciò accadrà troppo presto). Sto usando un X-macro :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

La maggior parte sta definendo e indefinendo i simboli che l'utente passerà come parametro a X-marco tramite un include. L'uso è così

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Dimostrazione dal vivo

Nota che non ho ancora incluso la scelta del tipo sottostante. Finora non ne avevo bisogno, ma dovrebbe essere semplice modificare il codice per abilitarlo.

Solo dopo aver scritto questo mi sono reso conto che è piuttosto simile alla risposta di eferioni . Forse l'ho letto prima e forse era la principale fonte di ispirazione. Non riuscivo sempre a capire le macro X fino a quando non ho scritto le mie;).


1

Soluzioni che utilizzano enum all'interno di class / struct (predefiniti di struttura con membri pubblici) e operatori sovraccarichi:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

Dall'esterno sembra quasi esattamente un enum di classe:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Questo produrrà "rosso 1 2". Potresti eventualmente sovraccaricare << per rendere l'output blu una stringa (anche se potrebbe causare ambiguità, quindi non è possibile), ma non funzionerebbe con Color :: GREEN poiché non si converte automaticamente in Color.

Lo scopo di avere una conversione implicita in Enum (che si converte implicitamente in int o tipo dato) è di essere in grado di fare:

Color color;
switch (color) ...

Funziona, ma significa anche che funziona anche:

int i = color;

Con una classe enum non si compila. Dovresti stare attento se sovraccarichi due funzioni prendendo l'enum e un intero, o rimuovi la conversione implicita ...

Un'altra soluzione prevede l'utilizzo di una classe enum effettiva e di membri statici:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Probabilmente occupa più spazio ed è più lungo da creare, ma causa un errore di compilazione per conversioni int implicite. Userei questo per questo!

C'è sicuramente un sovraccarico in questo, ma penso che sia solo più semplice e abbia un aspetto migliore rispetto ad altri codici che ho visto. C'è anche il potenziale per l'aggiunta di funzionalità, che potrebbero essere tutte nell'ambito della classe.

Modifica : funziona e la maggior parte può essere compilata prima dell'esecuzione:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

Questo è molto interessante :-) Comunque la tua versione attuale implica che devi scrivere manualmente il materiale case Enum::RED: return "red";. La domanda è sull'automatizzazione di queste cose da parte del compilatore (al momento della compilazione). L'idea della domanda è di cambiare o aggiungere valori enum solo senza dover aggiornare il materiale toString(). Vedi? Grazie
olibre il

1

Soluzione molto semplice con un grande vincolo: non puoi assegnare valori personalizzati ai enumvalori, ma con la giusta regex potresti farlo. puoi anche aggiungere una mappa per tradurli in enumvalori senza molto sforzo:

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

Esempio di utilizzo:

EnumToString(MyEnum, Red, Green, Blue);

Grazie Malem per la tua idea innovativa. Ho modificato la tua risposta per migliorare la leggibilità. Spero che ti piacciano i miei cambiamenti. Continua a migliorare la tua risposta: (1) estendi la sezione "Esempio di utilizzo" con qualcosa del tipo auto name = MyEnumStrings["Red"];- (2) Perché usi enum class? - (3) Supportate enum class MyEnum : char { Red, Green, Blue };? - (4) Spiega la funzione split()- (5) Hai bisogno di un parametro const std::regex& delim? - (6) Che ne dici di generare MyEnumStringsal momento della compilazione? => Puoi usare constexpr? ... Cin cin :-)
olibre,

Mi piace molto questo approccio. Davvero breve e facile da capire.
Anton Holmberg,

1

EDIT: controlla sotto per una versione più recente

Come accennato in precedenza, N4113 è la soluzione finale a questo problema, ma dovremo aspettare più di un anno per vederlo uscire.

Nel frattempo, se si desidera tale funzionalità, è necessario ricorrere a modelli "semplici" e ad alcune magie del preprocessore.

enumeratore

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

uso

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Spiegazione semplice

Enum<T>::m_counterè impostato su 0 all'interno di ogni dichiarazione dello spazio dei nomi.
( Qualcuno potrebbe indicarmi dove ^^ questo comportamento ^^ è menzionato nello standard? )
La magia del preprocessore automatizza la dichiarazione degli enumeratori.

svantaggi

  • Non è un enumtipo vero , quindi non promotabile a int
  • Non può essere utilizzato in casi di commutazione

Soluzione alternativa

Questo sacrifica la numerazione delle righe (non proprio) ma può essere utilizzata su casi switch .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

Errata

#line 0è in conflitto con -pedanticGCC e clang.

Soluzione

Inizia da #line 1e sottrai 1 da __LINE__.
Oppure, non usare -pedantic.
E mentre ci siamo, evitiamo VC ++ a tutti i costi, è sempre stato uno scherzo di un compilatore.

uso

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Implementazione e utilizzo nella vita reale

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Riferimento rapido

#line lineno - cppreference.com


0

Ho scritto una libreria per risolvere questo problema, tutto accade in fase di compilazione, tranne che per ottenere il messaggio.

Uso:

Usa macro DEF_MSGper definire una coppia di macro e messaggi:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKè la macro da utilizzare ed "OK!"è il messaggio corrispondente.

Usa get_message()o solo gm()per ricevere il messaggio:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

Utilizzare MSG_NUMper scoprire quante macro sono state definite. Questo automaticamente aumenterà, non devi fare nulla.

Messaggi predefiniti:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Progetto: libcodemsg


La libreria non crea dati extra. Tutto accade in tempo di compilazione. In message_def.h, genera un enumchiamato MSG_CODE; in message_def.c, genera una variabile che contiene tutte le stringhe in static const char* _g_messages[].

In tal caso, la libreria è limitata a crearne una enumsola. Questo è l'ideale per i valori di ritorno, ad esempio:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Un'altra cosa che mi piace di questo design è che puoi gestire le definizioni dei messaggi in diversi file.


Ho trovato la soluzione a questa domanda molto migliore.


Ciao Madwyn. Grazie per la tua idea Ma come funziona? Qual è il sovraccarico? (zero sovraccarico o crea dati extra?). La tua proposta sembra a posto, ma sfortunatamente, una dichiarazione DEF_MSGdeve essere usata / aggiornata / mantenuta per ogni enumvalore: - / E questo è ciò che idealmente vorremmo smettere di fare ... Cin cin
olibre

Grazie per la risposta, @olibre. Si prega di controllare la risposta aggiornata. Non vedo sovraccarico qui, tranne che è necessaria una chiamata di funzione per accedere alle stringhe. DEF_MSGrende enumstrettamente associato al messaggio, sebbene abbia alcune limitazioni.
Madwyn,

Grazie per la spiegazione allegata nella tua risposta :-) La tua libreria è a posto ma non può essere utilizzata per più enumerazioni: - / Che dire del supporto di enum class(C ++ 11) ? È possibile utilizzare constexprper limitare _g_messagesin fase di esecuzione. Supporta più enumtipi (evitando _g_messages) usando la meta-programmazione (tipo che trasporta {tipo-enum, valore-enum}) o forse variabili template (C ++ 14) . Penso che la tua lib non si adatti (ancora?) Ai requisiti C ++ 11/14/17. Cosa ne pensi? Saluti ;-)
olibre

1
Grazie per il seguito. Ho imparato qualcosa di nuovo oggi! La classe enum e le variabili template sembrano buone. Penso che la mia risposta sia stata un po '"fuori tema" dato che era aromatizzata C.
Madwyn,

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

esempio

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

automaticamente la macro ENUM_MAKE genera 'enum class' e helper class con 'enum reflection function'.

Al fine di ridurre gli errori, tutto viene definito con un solo ENUM_MAKE.

Il vantaggio di questo codice viene creato automaticamente per la riflessione e uno sguardo ravvicinato al codice macro, codice di facile comprensione. 'enum to string', 'stringa to enum' le prestazioni sono entrambe l'algoritmo O (1).

Gli svantaggi sono al primo utilizzo, la classe helper per il vettore di stringa e la mappa di enum relection viene inizializzata. ma se vuoi sarai anche preinizializzato. -


Sebbene questo codice possa rispondere alla domanda, sarebbe meglio spiegare come risolve il problema senza introdurre altri e perché utilizzarlo. Le risposte di solo codice non sono utili a lungo termine.
JAL

ciao ragazzi, mi dispiace non parlare molto bene l'inglese.
desperado_98

automaticamente la macro ENUM_MAKE genera 'enum class' e helper class con 'enum reflection function'. / Al fine di ridurre gli errori, tutto è definito con un solo ENUM_MAKE. Il vantaggio di questo codice viene creato automaticamente per la riflessione e uno sguardo ravvicinato al codice macro, codice di facile comprensione. 'enum to string', 'stringa to enum' le prestazioni sono entrambe l'algoritmo O (1). Gli svantaggi sono al primo utilizzo, la classe helper per il vettore di stringa e la mappa di enum relection viene inizializzata. ma se vuoi sarai anche preinizializzato.
desperado_98,

Ciao desperado_98. Grazie per il tuo contributo. Modifica la tua risposta e inserisci in essa il contenuto del tuo commento. Il compilatore può calcolare il tuo esempio in fase di compilazione se usi alcuni trucchi di meta-programmazione e constexpr. Intendo le funzioni toName()e toType()possono essere valutate durante la compilazione e non durante l'esecuzione (runtime). Si prega di adottare lo stile C ++ 11/14/17 nella risposta. Saluti ;-)
olibre

Inoltre: la tua macro è compatibile con enum class MyEnum : short { A, B, C };?
olibre,

0

la mia soluzione è senza utilizzo di macro.

vantaggi:

  • vedi esattamente quello che fai
  • l'accesso è con le mappe hash, quindi buono per molti enumerati
  • non è necessario considerare l'ordine o valori non consecutivi
  • sia enum a stringa che stringa a enum traduzione, mentre il valore enum aggiunto deve essere aggiunto in un solo posto aggiuntivo

svantaggi:

  • è necessario replicare tutti i valori enum come testo
  • l'accesso nella mappa hash deve considerare il caso stringa
  • manutenzione se l'aggiunta di valori è dolorosa - deve essere aggiunta in enum e nella mappa di traduzione diretta

quindi ... fino al giorno in cui C ++ implementerà la funzionalità Enum.Parse di C #, rimarrò bloccato con questo:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Bene, ancora un'altra opzione. Un tipico caso d'uso è il caso in cui siano necessarie costanti per i verbi HTTP, nonché l'utilizzo dei valori della versione stringa.

L'esempio:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

La classe VERB:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

1
Per ridurre l'utilizzo della memoria, è possibile sostituire il membro const std::string textsemplicemente theStrings[v]. Tuttavia la domanda riguarda le funzionalità di C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 per evitare di dover scrivere tale classe a mano: - /
olibre

0

La mia risposta è qui

È possibile ottenere nomi di valori enum e questi indici contemporaneamente come deque of string.

Questo metodo richiede solo poca copia e incolla e modifica.

Il risultato ottenuto richiede il cast di tipo da size_t al tipo di classe enum quando è necessario il valore del tipo di classe enum, ma penso che sia un modo molto portatile e potente per trattare la classe enum.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

È possibile utilizzare una libreria di riflessione, come Ponder :

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

0

(Analogo di https://stackoverflow.com/a/54967187/2338477 , leggermente modificato).

Ecco la mia soluzione con il minimo definisci la magia e il supporto dei singoli compiti di enum.

Ecco il file di intestazione:

#pragma once
#include <string>
#include <map>
#include <regex>

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

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // 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...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

Ed ecco un esempio di applicazione di prova:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

La versione aggiornata dello stesso file di intestazione verrà mantenuta qui:

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


-5

Che dire di un semplice sovraccarico di streaming? Devi ancora mantenere la mappatura se non vuoi fare un po 'di macro magia, ma la trovo più pulita della tua soluzione originale.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) crea ancora più duplicazioni 2) ti costringe a usare i flussi
Karoly Horvath

6
-1 Mi dispiace @dau_sama, ma lo scopo di tutte queste enum per stringere domande ricorrenti è evitare il mantenimento del mapping enum / string. Se ritieni che la tua risposta non sia adatta allo scopo, ti preghiamo di considerare di eliminare la risposta. Buona fortuna per la tua prossima risposta;) Saluti
olibre

-9

La via più facile?
Usa Ada: Enumeration'Image( Value )fa esattamente quello che vuoi. Se hai davvero bisogno di C ++ puoi provare ad esportare la funzione:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
In che modo questo risponde alla domanda? La domanda afferma chiaramente la conversione di un enum in stringa nel moderno C ++.
Michael Choi,

1
@MichaelChoi - Lo fa, ma c'è anche il problema di utilizzare lo strumento adeguato per il lavoro: solo perché C ++ è turing-complete e quindi può risolvere tutti i problemi risolvibili NON significa che la soluzione sia: veloce, mantenibile o efficiente. Usare una lingua che ha la funzionalità corretta / desiderata ed esportarla è una soluzione valida.
Shark8,

3
Nella prima frase della domanda "questa domanda riguarda l'uso delle nuove funzionalità C ++". poi "[Non ho ancora trovato] un modo elegante usando le nuove funzionalità di C ++ 11, C ++ 14 o C ++ 17". L'autore stava chiaramente cercando una soluzione C ++; hai dato una soluzione in Ada, quindi non hai risposto alla domanda. Stai suggerendo di incorporare una dipendenza completamente diversa per risolvere qualcosa che probabilmente non rientrava nell'ambito delle domande.
Michael Choi,
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.