Jak łatwo mapować wyliczenia C ++ na ciągi

119

Mam kilka typów wyliczeń w niektórych plikach nagłówkowych bibliotek, których używam, i chcę mieć sposób konwertowania wartości wyliczenia na ciągi użytkownika - i odwrotnie.

RTTI nie zrobi tego za mnie, ponieważ „ciągi użytkowników” muszą być nieco bardziej czytelne niż wyliczenia.

Rozwiązaniem brutalnej siły byłoby kilka takich funkcji, ale wydaje mi się, że jest to trochę zbyt podobne do C.

enum MyEnum {VAL1, VAL2,VAL3};

String getStringFromEnum(MyEnum e)
{
  switch e
  {
  case VAL1: return "Value 1";
  case VAL2: return "Value 2";
  case VAL1: return "Value 3";
  default: throw Exception("Bad MyEnum");
  }
}

Mam przeczucie, że istnieje eleganckie rozwiązanie wykorzystujące szablony, ale nie mogę jeszcze tego ogarnąć.

AKTUALIZACJA: Dzięki za sugestie - powinienem był wyjaśnić, że wyliczenia są zdefiniowane w nagłówku biblioteki innej firmy, więc nie chcę zmieniać ich definicji.

Mam teraz przeczucie, że unikam szablonów i robię coś takiego:

char * MyGetValue(int v, char *tmp); // implementation is trivial

#define ENUM_MAP(type, strings) char * getStringValue(const type &T) \
 { \
 return MyGetValue((int)T, strings); \
 }

; enum eee {AA,BB,CC}; - exists in library header file 
; enum fff {DD,GG,HH}; 

ENUM_MAP(eee,"AA|BB|CC")
ENUM_MAP(fff,"DD|GG|HH")

// To use...

    eee e;
    fff f;
    std::cout<< getStringValue(e);
    std::cout<< getStringValue(f);
Roddy
źródło
1
Możliwy duplikat Czy istnieje prosty sposób przekonwertowania wyliczenia w języku C ++ na ciąg?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

60

Jeśli chcesz, aby wyliczenia same się nazywały jako ciągi, zobacz ten post . W przeciwnym razie astd::map<MyEnum, char const*> będzie dobrze działać. (Nie ma sensu kopiowanie literałów ciągów do std :: strings na mapie)

Aby uzyskać dodatkowy cukier składniowy, oto jak napisać klasę map_init. Celem jest pozwolenie

std::map<MyEnum, const char*> MyMap;
map_init(MyMap)
    (eValue1, "A")
    (eValue2, "B")
    (eValue3, "C")
;

Funkcja template <typename T> map_init(T&)zwraca map_init_helper<T>. map_init_helper<T>przechowuje T & i definiuje trywialne map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&). (Wracając *thisz operator()pozwala na łączenie operator(), jak operator<<na std::ostreams)

template<typename T> struct map_init_helper
{
    T& data;
    map_init_helper(T& d) : data(d) {}
    map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value)
    {
        data[key] = value;
        return *this;
    }
};

template<typename T> map_init_helper<T> map_init(T& item)
{
    return map_init_helper<T>(item);
}

Ponieważ funkcja i klasa pomocnicza są oparte na szablonach, można ich użyć do dowolnej mapy lub struktury podobnej do mapy. To znaczy może również dodawać wpisy dostd::unordered_map

Jeśli nie lubisz pisać tych pomocników, boost :: assign oferuje te same funkcje po wyjęciu z pudełka.

MSalters
źródło
Masz rację, odnosząc się do innego pytania. Ludzie powinni zapoznać się z „pytaniami pokrewnymi” przed wysłaniem…
xtofl
2
@xtofl: „Pytania pokrewne” pokazane tutaj są zupełnie inne od pytań pokrewnych, które wyszczególniłem, kiedy opublikowałem pytanie!
Roddy,
@MSalters, std :: map to przydatny sposób obsługi implementacji, ale szukam sposobów na zmniejszenie standardowego kodu, który może wymagać.
Roddy,
@MSalters, byłoby miło móc zaakceptować wiele argumentów dla operatora []. ale niestety nie można tego zrobić. x [a, b] oblicza się do x [b]. wyrażenie (a, b) używa operatora przecinka. jest więc odpowiednikiem ["A"] ["B"] ["C"] w twoim kodzie. możesz to zmienić na [eValue1] ["A"] [eValu ..
Johannes Schaub - litb
operator wywołania funkcji byłby również dobrym kandydatem: map_init (MyMap) (eValue1, "A") (eValue2, "B") .... wtedy jest to równoważne z boost :: assign: insert (MyMap) (eValue1, „A”) (eValue2, „B”) ... ( boost.org/doc/libs/1_35_0/libs/ assign / doc / index.html )
Johannes Schaub - litb
31

Rozwiązanie MSalters jest dobre, ale w zasadzie ponownie wdraża boost::assign::map_list_of. Jeśli masz doładowanie, możesz go użyć bezpośrednio:

#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

using boost::assign::map_list_of;

enum eee { AA,BB,CC };

const boost::unordered_map<eee,const char*> eeeToString = map_list_of
    (AA, "AA")
    (BB, "BB")
    (CC, "CC");

int main()
{
    std::cout << " enum AA = " << eeeToString.at(AA) << std::endl;
    return 0;
}
Alastair
źródło
Jak byś tego użył, gdy eeeToString jest członkiem danych klasy? Otrzymuję komunikat „Błąd: inicjalizacja elementu członkowskiego danych jest niedozwolona”
Użytkownik
@User: Członkowie danych klasy są inicjowani w konstruktorach, zwykle na liście inicjalizacyjnej.
MSalters
Czy istnieje sposób, aby to zadziałało dla wszystkich wyliczeń. Mam wiele deklaracji wyliczenia i nie chcę, aby mapa działała tylko dla typu eeew Twoim przypadku.
Justin Liang
Próbowałem za pomocą szablonu, ale potem dostał i błąd: error: template declaration of 'const boost::unordered::unordered_map<T, const char*> enumToString'.
Justin Liang
4
W rzeczywistości ta odpowiedź jest w dużej mierze przestarzała w C ++ 11.
Alastair
19

Automatycznie generuj jeden formularz z innego.

Źródło:

enum {
  VALUE1, /* value 1 */
  VALUE2, /* value 2 */
};

Wygenerowano:

const char* enum2str[] = {
  "value 1", /* VALUE1 */
  "value 2", /* VALUE2 */
};

Jeśli wartości wyliczenia są duże, wygenerowany formularz może używać unordered_map <> lub szablonów, zgodnie z sugestią Constantina.

Źródło:

enum State{
  state0 = 0, /* state 0 */
  state1 = 1, /* state 1 */
  state2 = 2, /* state 2 */
  state3 = 4, /* state 3 */

  state16 = 0x10000, /* state 16 */
};

Wygenerowano:

template <State n> struct enum2str { static const char * const value; };
template <State n> const char * const enum2str<n>::value = "error";

template <> struct enum2str<state0> { static const char * const value; };
const char * const enum2str<state0>::value = "state 0";

Przykład:

#include <iostream>

int main()
{
  std::cout << enum2str<state16>::value << std::endl;
  return 0;
}
jfs
źródło
Chociaż jest najszybszy, nie jest tak łatwy jak @MSalters.
kenny
2
Dzieje się tak, jeśli masz trochę perla / pythona do odczytania listy ciągów z pliku tekstowego i wygenerowania pliku .h ze statycznym char w czasie kompilacji. = "Pisz programy do pisania programów"
Martin Beckett,
@mgb: perl / python nie są jedynymi opcjami, które zrobi prawie każdy silnik szablonów w dowolnym języku (w tym przypadku generuje się oba formularze z szablonu).
jfs
@jf. Tak, ważnym punktem było automatyczne tworzenie statycznych tabel danych w czasie kompilacji. Prawdopodobnie wolałbym po prostu wygenerować głupią tablicę statyczną.
Martin Beckett,
Czy to zadziała, jeśli State nie jest znany w czasie kompilacji? Jestem prawie pewien, że tak się nie stanie - teoretycznie kompilator musiałby utworzyć wystąpienie szablonu enum2str ze wszystkimi możliwymi wartościami wyliczenia, czego jestem prawie pewien, że gcc (przynajmniej) nie zrobi.
Alastair
11

Pamiętam, że odpowiedziałem na to gdzie indziej w StackOverflow. Powtarzam to tutaj. Zasadniczo jest to rozwiązanie oparte na wariadycznych makrach i jest dość łatwe w użyciu:

#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
        else if(str[i] == ',') { \
        strings.push_back(temp.str()); \
        temp.str(std::string());\
        } \
        else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;} 

Aby użyć go w swoim kodzie, po prostu wykonaj:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);
auto dog = Animal::DOG;
std::cout<<dog;
Debdatta Basu
źródło
1
Po prostu zmień deklarację klasy wyliczenia na enum, aby działała na pre c ++ 11.
Debdatta Basu
1
Masz rację, to działa (auto to też tylko c ++ 11). Niezłe rozwiązanie! Byłoby idealnie, gdybyś mógł również ustawić wartość dla niektórych
wyliczeń
Wydaje mi się, że widziałem coś takiego w doładowaniu
Siergiej
10

Proponuję połączenie X-makr to najlepsze rozwiązanie i następujące funkcje szablonu:

Pożyczyć od marcinkoziukmyopenidcom i rozszerzony

enum Colours {
#   define X(a) a,
#   include "colours.def"
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
#   include "colours.def"
#   undef X
    0
};

template <class T> T str2enum( const char* );
template <class T> const char* enum2str( T );

#define STR2ENUM(TYPE,ARRAY) \
template <> \
TYPE str2enum<TYPE>( const char* str ) \
    { \
    for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \
        if( !strcmp( ARRAY[i], str ) ) \
            return TYPE(i); \
    return TYPE(0); \
    }

#define ENUM2STR(TYPE,ARRAY) \
template <> \
const char* enum2str<TYPE>( TYPE v ) \
    { \
    return ARRAY[v]; \
    }

#define ENUMANDSTR(TYPE,ARRAY)\
    STR2ENUM(TYPE,ARRAY) \
    ENUM2STR(TYPE,ARRAY)

ENUMANDSTR(Colours,colours_str)

colour.def

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
David Allan Finch
źródło
Czy istnieje sposób, aby definicja tablicy ciągów wyliczeniowych była ogólna? (Nie wiem, jak obsługiwać X-Macro w makrze i nie radzę sobie łatwo z szablonem)
Jonathan
5

Korzystam z tego rozwiązania, które poniżej przedstawiam:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}
Juan Gonzalez Burgos
źródło
1
To są podstawowe makra X i jestem zdumiony, że jest to pierwsza odpowiedź, która to zasugeruje! +1
Wyścigi lekkości na orbicie
4

Jeśli chcesz uzyskać reprezentacje łańcuchowe MyEnum zmiennych , szablony tego nie wycinają. Szablon może być wyspecjalizowany w wartościach całkowitych znanych w czasie kompilacji.

Jeśli jednak tego chcesz, spróbuj:

#include <iostream>

enum MyEnum { VAL1, VAL2 };

template<MyEnum n> struct StrMyEnum {
    static char const* name() { return "Unknown"; }
};

#define STRENUM(val, str) \
  template<> struct StrMyEnum<val> { \
    static char const* name() { return str; }};

STRENUM(VAL1, "Value 1");
STRENUM(VAL2, "Value 2");

int main() {
  std::cout << StrMyEnum<VAL2>::name();
}

To jest rozwlekłe, ale wykryje błędy takie jak ten, który popełniłeś - Twój case VAL1jest duplikatem.

Constantin
źródło
W rzeczywistości nazwa metody () nie jest konieczna. Zobacz moją odpowiedź.
jfs
3

Spędziłem więcej czasu na badaniu tego tematu, do którego chciałbym się przyznać. Na szczęście na wolności istnieją świetne rozwiązania open source.

To są dwa świetne podejścia, nawet jeśli nie są wystarczająco (jeszcze) znane,

wise_enum

  • Samodzielna inteligentna biblioteka wyliczeń dla C ++ 11/14/17. Obsługuje wszystkie standardowe funkcje, których można oczekiwać od inteligentnej klasy enum w C ++.
  • Ograniczenia: wymaga co najmniej C ++ 11.

Better Enums

  • Refleksyjna biblioteka wyliczeń czasu kompilacji z czystą składnią, w jednym pliku nagłówkowym i bez zależności.
  • Ograniczenia: oparty na makrach, nie może być używany wewnątrz klasy.
jose.angel.jimenez
źródło
2

Chciałbym mieć mapę m - i osadzić ją w enum.

konfiguracja z m [MyEnum.VAL1] = "Wartość 1";

i wszystko gotowe.

Richard Harrison
źródło
2

Kilkakrotnie potrzebowałem tej funkcji do debugowania / analizowania kodu od innych. W tym celu napisałem skrypt Perla, który generuje klasę z kilkoma przeciążonymi toStringmetodami. Każda toStringmetoda przyjmuje Enumargument jako argument i zwraca const char*.

Oczywiście skrypt nie analizuje samego C ++ pod kątem wyliczeń, ale używa ctags do generowania tablicy symboli.

Skrypt Perla jest tutaj: http://heinitz-it.de/download/enum2string/enum2string.pl.html

Valentin Heinitz
źródło
2

Twoje odpowiedzi zainspirowały mnie do samodzielnego napisania makr. Moje wymagania były następujące:

  1. zapisuj każdą wartość wyliczenia tylko raz, więc nie ma podwójnych list do obsługi

  2. nie przechowuj wartości wyliczenia w osobnym pliku, który jest później # uwzględniony, więc mogę zapisać go w dowolnym miejscu

  3. nie zastępuj samego wyliczenia, nadal chcę mieć zdefiniowany typ wyliczenia, ale oprócz tego chcę mieć możliwość mapowania każdej nazwy wyliczenia na odpowiedni ciąg (aby nie wpływać na starszy kod)

  4. wyszukiwanie powinno być szybkie, więc najlepiej bez przełączników dla tych ogromnych wyliczeń

Ten kod tworzy klasyczne wyliczenie z pewnymi wartościami. Ponadto tworzy jako std :: map, które mapuje każdą wartość wyliczenia na jej nazwę (tj. Map [E_SUNDAY] = "E_SUNDAY" itp.)

Ok, oto kod teraz:

EnumUtilsImpl.h :

map<int, string> & operator , (map<int, string> & dest, 
                               const pair<int, string> & keyValue) {
    dest[keyValue.first] = keyValue.second; 
    return dest;
}

#define ADD_TO_MAP(name, value) pair<int, string>(name, #name)

EnumUtils.h // to jest plik, który chcesz dołączyć za każdym razem, gdy będziesz musiał to zrobić, użyjesz z niego makr:

#include "EnumUtilsImpl.h"
#define ADD_TO_ENUM(name, value) \
    name value

#define MAKE_ENUM_MAP_GLOBAL(values, mapName) \
    int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;}  \
    int __makeMapTmp##mapName = __makeMap##mapName();

#define MAKE_ENUM_MAP(values, mapName) \
    mapName, values(ADD_TO_MAP);

MyProjectCodeFile.h // to jest przykład tego, jak używać go do tworzenia niestandardowego wyliczenia:

#include "EnumUtils.h*

#define MyEnumValues(ADD) \
    ADD(val1, ), \
    ADD(val2, ), \
    ADD(val3, = 100), \
    ADD(val4, )

enum MyEnum {
    MyEnumValues(ADD_TO_ENUM)
};

map<int, string> MyEnumStrings;
// this is how you initialize it outside any function
MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); 

void MyInitializationMethod()
{ 
    // or you can initialize it inside one of your functions/methods
    MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); 
}

Twoje zdrowie.

muqker
źródło
2

Oto próba automatycznego uzyskania operatorów strumieni << i >> na wyliczeniu za pomocą makra z jednym wierszem ...

Definicje:

#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>

#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str

#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)

#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
    attribute std::istream& operator>>(std::istream& is, name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        std::string str; \
        std::istream& r = is >> str; \
        const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
        const std::vector<std::string> enumStr(name##Str, name##Str + len); \
        const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
        if (it != enumStr.end())\
            e = name(it - enumStr.begin()); \
        else \
            throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
        return r; \
    }; \
    attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        return (os << name##Str[e]); \
    }

Stosowanie:

// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);

class Essai {
public:
    // Declare enum inside class
    enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);

};

int main() {
    std::cout << Essai::Item1 << std::endl;

    Essai::Test ddd = Essai::Item1;
    std::cout << ddd << std::endl;

    std::istringstream strm("Item2");
    strm >> ddd;

    std::cout << (int) ddd << std::endl;
    std::cout << ddd << std::endl;
}

Nie jestem pewien co do ograniczeń tego schematu ... komentarze są mile widziane!

OlivierB
źródło
1

w nagłówku:

enum EFooOptions
 {
FooOptionsA = 0, EFooOptionsMin = 0,
FooOptionsB,
FooOptionsC,
FooOptionsD 
EFooOptionsMax
};
extern const wchar* FOO_OPTIONS[EFooOptionsMax];

w pliku .cpp:

const wchar* FOO_OPTIONS[] = {
    L"One",
    L"Two",
    L"Three",
    L"Four"
};

Uwaga: nie obsługuj złego indeksu tablicy. :) Ale możesz łatwo dodać funkcję, która zweryfikuje wyliczenie przed pobraniem ciągu z tablicy.

moogs
źródło
Rzeczywiście rozwiązanie bardzo nie-DRY-SPOT.
xtofl
teraz, gdy wspomniałeś o SUCHE. pliki .h i .cpp generowane automatycznie z innego pliku wejściowego. Chciałbym zobaczyć lepsze rozwiązania (które nie uciekają się do niepotrzebnej złożoności)
moogs
1

Chciałem tylko pokazać to możliwe eleganckie rozwiązanie za pomocą makr. To nie rozwiązuje problemu, ale myślę, że jest to dobry sposób na ponowne przemyślenie problemu.

#define MY_LIST(X) X(value1), X(value2), X(value3)

enum eMyEnum
    {
    MY_LIST(PLAIN)
    };

const char *szMyEnum[] =
    {
    MY_LIST(STRINGY)
    };


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

std::cout << szMyEnum[value1] << value1 <<" " <<  szMyEnum[value2] << value2 << std::endl;

return 0;
}

---- EDYTOWAĆ ----

Po przeszukiwaniu internetu i własnych doświadczeniach doszedłem do następującego rozwiązania:

//this is the enum definition
#define COLOR_LIST(X) \
  X( RED    ,=21)      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE , =242)      \
  X( ORANGE )      \
  X( YELLOW )

//these are the macros
#define enumfunc(enums,value) enums,
#define enumfunc2(enums,value) enums value,
#define ENUM2SWITCHCASE(enums) case(enums): return #enums;

#define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)};
#define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}}
#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)};

//here the enum and the string enum map table are generated
AUTOENUM(testenum,COLOR_LIST)
ENUM2STRTABLE(testfunenum,COLOR_LIST)
ENUM2STRUCTINFO(colorinfo,COLOR_LIST)//colorinfo structur {int values[]; int N; char * enum2str(int);}

//debug macros
#define str(a) #a
#define xstr(a) str(a)


int main( int argc, char** argv )
{
testenum x = YELLOW;
std::cout << testfunenum(GREEN) << "   " << testfunenum(PURPLE) << PURPLE << "  " << testfunenum(x);

for (int i=0;i< colorinfo::N;i++)
std::cout << std::endl << colorinfo::values[i] <<  "  "<< colorinfo::enum2str(colorinfo::values[i]);

  return EXIT_SUCCESS;
}

Chciałem tylko opublikować, może ktoś mógłby uznać to rozwiązanie za przydatne. Nie ma potrzeby stosowania klas szablonów, nie ma potrzeby stosowania języka C ++ 11 ani wzmocnienia, więc można go również użyć w prostym C.

---- EDIT2 ----

tabela informacji może powodować pewne problemy przy użyciu więcej niż 2 wyliczeń (problem kompilatora). Następujące obejście zadziałało:

#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)};
jamk
źródło
1
typedef enum {
    ERR_CODE_OK = 0,
    ERR_CODE_SNAP,

    ERR_CODE_NUM
} ERR_CODE;

const char* g_err_msg[ERR_CODE_NUM] = {
    /* ERR_CODE_OK   */ "OK",
    /* ERR_CODE_SNAP */ "Oh, snap!",
};

Powyżej jest moje proste rozwiązanie. Jedną z jego zalet jest „NUM”, który kontroluje rozmiar tablicy wiadomości, a także zapobiega dostępowi poza granicami (jeśli używasz go mądrze).

Możesz również zdefiniować funkcję, aby uzyskać ciąg:

const char* get_err_msg(ERR_CODE code) {
    return g_err_msg[code];
}

W nawiązaniu do mojego rozwiązania, następne okazało się całkiem interesujące. Ogólnie rozwiązał problem synchronizacji powyższego.

Slajdy tutaj: http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

Kod tutaj: https://github.com/arunksaha/enum_to_string

Madwyn
źródło
1

Wiem, że spóźniłem się na imprezę, ale dla każdego, kto odwiedza tę stronę, możesz spróbować, jest to łatwiejsze niż wszystko, co tam jest i ma więcej sensu:

namespace texs {
    typedef std::string Type;
    Type apple = "apple";
    Type wood = "wood";
}
uIM7AI9S
źródło
Czy sugerujesz użycie ciągów znaków, a nie wyliczenia w ogóle? To tak naprawdę nie rozwiązuje problemu.
Roddy,
0

Niedawno miałem ten sam problem z biblioteką dostawców (Fincad). Na szczęście dostawca dostarczył dokumentację XML dla wszystkich wyliczeń. Skończyło się na wygenerowaniu mapy dla każdego typu wyliczenia i udostępnieniu funkcji wyszukiwania dla każdego wyliczenia. Ta technika umożliwia również przechwycenie wyszukiwania poza zakresem wyliczenia.

Jestem pewien, że swig mógłby zrobić dla ciebie coś podobnego, ale z przyjemnością dostarczę narzędzia do generowania kodu, które są napisane w języku ruby.

Oto przykład kodu:

std::map<std::string, switches::FCSW2::type> init_FCSW2_map() {
        std::map<std::string, switches::FCSW2::type> ans;
        ans["Act365Fixed"] = FCSW2::Act365Fixed;
        ans["actual/365 (fixed)"] = FCSW2::Act365Fixed;
        ans["Act360"] = FCSW2::Act360;
        ans["actual/360"] = FCSW2::Act360;
        ans["Act365Act"] = FCSW2::Act365Act;
        ans["actual/365 (actual)"] = FCSW2::Act365Act;
        ans["ISDA30360"] = FCSW2::ISDA30360;
        ans["30/360 (ISDA)"] = FCSW2::ISDA30360;
        ans["ISMA30E360"] = FCSW2::ISMA30E360;
        ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360;
        return ans;
}
switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) {
        static std::map<std::string, switches::FCSW2::type> switch_map = init_FCSW2_map();
        std::map<std::string, switches::FCSW2::type>::iterator it = switch_map.find(fincad_switch);
        if(it != switch_map.end()) {
                return it->second;
        } else {
                throw FCSwitchLookupError("Bad Match: FCSW2");
        }
}

Wygląda na to, że chcesz przejść w drugą stronę (wyliczenie do łańcucha zamiast ciągu do wyliczenia), ale odwrócenie tego powinno być trywialne.

-Odrobina


źródło
1
a) Czy ktoś inny uważa to za absolutnie nieczytelne? Kilka typedefów i użycie deklaracji znacznie poprawiłoby czytelność. b) lokalne deklaracje statyczne nie są bezpieczne dla wątków. c) użyj const string & zamiast char *, d) co z dołączeniem wartości, której nie można znaleźć w zgłoszonym wyjątku?
Alastair
0

Sprawdź, czy odpowiada Ci następująca składnia:

// WeekEnd enumeration
enum WeekEnd
{
    Sunday = 1,
    Saturday = 7
};

// String support for WeekEnd
Begin_Enum_String( WeekEnd )
{
    Enum_String( Sunday );
    Enum_String( Saturday );
}
End_Enum_String;

// Convert from WeekEnd to string
const std::string &str = EnumString<WeekEnd>::From( Saturday );
// str should now be "Saturday"

// Convert from string to WeekEnd
WeekEnd w;
EnumString<WeekEnd>::To( w, "Sunday" );
// w should now be Sunday

Jeśli tak, możesz zapoznać się z tym artykułem:
http://www.gamedev.net/reference/snippets/features/cppstringizing/


źródło
0

ten stary bałagan to mój wysiłek oparty na bitach i fragmentach z SO. Element for_each musiałby zostać rozwinięty, aby obsługiwał więcej niż 20 wartości wyliczeniowych. Przetestowałem to na Visual Studio 2019, Clang i GCC. c ++ 11

#define _enum_expand(arg) arg
#define _enum_select_for_each(_,_0, _1, _2,_3,_4, _5, _6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,N, ...) N
#define _enum_for_each_0(_call, arg0,arg1,...)
#define _enum_for_each_1(_call, arg0,arg1) _call(arg0,arg1)
#define _enum_for_each_2(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_1(_call,arg0, __VA_ARGS__))
#define _enum_for_each_3(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_2(_call,arg0, __VA_ARGS__))
#define _enum_for_each_4(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_3(_call,arg0, __VA_ARGS__))
#define _enum_for_each_5(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_4(_call,arg0, __VA_ARGS__))
#define _enum_for_each_6(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_5(_call,arg0, __VA_ARGS__))
#define _enum_for_each_7(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_6(_call,arg0, __VA_ARGS__))
#define _enum_for_each_8(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_7(_call,arg0, __VA_ARGS__))
#define _enum_for_each_9(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_8(_call,arg0, __VA_ARGS__))
#define _enum_for_each_10(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_9(_call,arg0, __VA_ARGS__))
#define _enum_for_each_11(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_10(_call,arg0, __VA_ARGS__))
#define _enum_for_each_12(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_11(_call,arg0, __VA_ARGS__))
#define _enum_for_each_13(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_12(_call,arg0, __VA_ARGS__))
#define _enum_for_each_14(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_13(_call,arg0, __VA_ARGS__))
#define _enum_for_each_15(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_14(_call,arg0, __VA_ARGS__))
#define _enum_for_each_16(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_15(_call,arg0, __VA_ARGS__))
#define _enum_for_each_17(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_16(_call,arg0, __VA_ARGS__))
#define _enum_for_each_18(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_17(_call,arg0, __VA_ARGS__))
#define _enum_for_each_19(_call, arg0,arg1, ...) _call(arg) _enum_expand(_enum_for_each_18(_call,arg0, __VA_ARGS__))
#define _enum_for_each(arg, ...) \
    _enum_expand(_enum_select_for_each(_, ##__VA_ARGS__, \
    _enum_for_each_19, _enum_for_each_18, _enum_for_each_17, _enum_for_each_16, _enum_for_each_15, \
    _enum_for_each_14, _enum_for_each_13, _enum_for_each_12, _enum_for_each_11, _enum_for_each_10, \
    _enum_for_each_9,  _enum_for_each_8,  _enum_for_each_7,  _enum_for_each_6,  _enum_for_each_5,  \
    _enum_for_each_4,  _enum_for_each_3,  _enum_for_each_2,  _enum_for_each_1,  _enum_for_each_0)(arg, ##__VA_ARGS__))

#define _enum_strip_args_1(arg0) arg0
#define _enum_strip_args_2(arg0, arg1) arg0, arg1
#define _enum_make_args(...) (__VA_ARGS__)

#define _enum_elem_arity1_1(arg) arg,
#define _enum_elem_arity1( ...) _enum_expand(_enum_elem_arity1_1 __VA_ARGS__)
#define _enum_elem_arity2_1(arg0,arg1) arg0 = arg1,
#define _enum_elem_arity2( ...) _enum_expand(_enum_elem_arity2_1 __VA_ARGS__)

#define _enum_elem_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_elem_select_arity_1(...) _enum_expand(_enum_elem_select_arity_2(__VA_ARGS__, _enum_elem_arity2,_enum_elem_arity1,_))
#define _enum_elem_select_arity(enum_type,...) _enum_expand(_enum_elem_select_arity_1 __VA_ARGS__)(__VA_ARGS__)

#define _enum_str_arity1_1(enum_type,arg) { enum_type::arg,#arg },
#define _enum_str_arity1(enum_type,...) _enum_expand(_enum_str_arity1_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_1 __VA_ARGS__)))
#define _enum_str_arity2_1(enum_type,arg,value) { enum_type::arg,#arg },
#define _enum_str_arity2(enum_type, ...) _enum_expand(_enum_str_arity2_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_2 __VA_ARGS__)))
#define _enum_str_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_str_select_arity_1(...) _enum_expand(_enum_str_select_arity_2(__VA_ARGS__, _enum_str_arity2,_enum_str_arity1,_))
#define _enum_str_select_arity(enum_type,...) _enum_expand(_enum_str_select_arity_1 __VA_ARGS__)(enum_type,__VA_ARGS__)

#define error_code_enum(enum_type,...)  enum class enum_type {              \
    _enum_expand(_enum_for_each(_enum_elem_select_arity,enum_type, ##__VA_ARGS__))};  \
    namespace _ ## enum_type ## _detail { \
        template <typename> struct _ ## enum_type ## _error_code{ \
            static const std::map<enum_type, const char*> enum_type ## _map; \
        }; \
            template <typename T> \
            const std::map<enum_type, const char*> _ ## enum_type ## _error_code<T>::enum_type ## _map = { \
                _enum_expand(_enum_for_each(_enum_str_select_arity,enum_type,  ##__VA_ARGS__)) \
        }; \
    } \
    inline const char* get_error_code_name(const enum_type& value) { \
        return _ ## enum_type ## _detail::_ ## enum_type ## _error_code<enum_type>::enum_type ## _map.find(value)->second; \
    } 

error_code_enum(myenum,
    (one, 1),
    (two)
);

który tworzy następujący kod

enum class myenum { 
    one = 1,
    two,
};
namespace _myenum_detail {
    template <typename>
    struct _myenum_error_code {
        static const std::map<myenum, const char*> myenum_map;
    };
    template <typename T>
    const std::map<myenum, const char*> _myenum_error_code<T>::myenum_map = {
        { myenum::one, "one" }, 
        { myenum::two, "two" },
    };
}
inline const char* get_error_code_name(const myenum& value) { 
    return _myenum_detail::_myenum_error_code<myenum>::myenum_map.find(value)->second; 
}

Jaka szkoda, że ​​obręcze, które musisz przeskoczyć z preprocesorem, aby to zrobić w jednym z najczęściej używanych języków programowania na świecie ...

rmawatson
źródło
0

Używając wyznaczonych inicjatorów tablicy, tablica ciągów jest niezależna od kolejności elementów w wyliczeniu:

enum Values {
    Val1,
    Val2
};

constexpr string_view v_name[] = {
    [Val1] = "Value 1",
    [Val2] = "Value 2"
}
Daniel
źródło