Czy istnieje prosty sposób konwersji wyliczenia w języku C ++ na ciąg?


123

Załóżmy, że mamy kilka nazwanych wyliczeń:

enum MyEnum {
      FOO,
      BAR = 0x50
};

To, co wyszukałem w Google, to skrypt (w dowolnym języku), który skanuje wszystkie nagłówki w moim projekcie i generuje nagłówek z jedną funkcją na wyliczenie.

char* enum_to_string(MyEnum t);

I implementacja z czymś takim:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }

Gotcha jest tak naprawdę z wyliczeniami zdefiniowanymi przez typ i nienazwanymi wyliczeniami w stylu C. Czy ktoś coś na to wie?

EDYCJA: Rozwiązanie nie powinno modyfikować mojego źródła, z wyjątkiem wygenerowanych funkcji. Wyliczenia znajdują się w API, więc korzystanie z rozwiązań proponowanych do tej pory nie wchodzi w grę.


Odpowiedź o przeniesieniu fabryki opartej na makrach do stackoverflow.com/questions/147267/ ... - po zaktualizowaniu pytania nie ma już tutaj znaczenia.
Suma,

Odpowiedzi:


48

Możesz wypróbować GCCXML .

Uruchomienie GCCXML na przykładowym kodzie daje:

<GCC_XML>
  <Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
  <Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
  <Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
    <EnumValue name="FOO" init="0"/>
    <EnumValue name="BAR" init="80"/>
  </Enumeration>
  <File id="f0" name="my_enum.h"/>
</GCC_XML>

Możesz użyć dowolnego preferowanego języka, aby wyciągnąć tagi Enumeration i EnumValue i wygenerować żądany kod.


Doskonały! Działał jak urok z prostym skryptem w języku Python. Dzięki.
Edu Felipe

6
+1, GCCXML wygląda bardzo ładnie! (Chociaż prawie -1edłem, ponieważ początkowo źle to odczytałem jako sugestię, aby użyć powyższej pełnej składni XML do zakodowania twojego wyliczenia - rozwiązanie, które cuchnie nadmierną inżynierią!)
j_random_hacker

1
jakieś zmiany, które możesz opublikować w pythonie?
phillipwei

74

X-makra to najlepsze rozwiązanie. Przykład:

#include <iostream>

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

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c];
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

colours.def:

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)

Jednak zazwyczaj wolę następującą metodę, aby można było nieco poprawić ciąg.

#define X(a, b) a,
#define X(a, b) b,

X(Red, "red")
X(Green, "green")
// etc.

11
fajnie, chociaż nie podoba mi się dodatkowy plik
Ronny Brendel,

2
Tylko upewnij się, że proces kompilacji nie dodaje #pragma (raz) przed każdym plikiem dołączania ...
xtofl

24
Nie jestem pewien co do „najlepszego” rozwiązania!
Wyścigi lekkości na orbicie

2
To rozwiązanie jest po prostu znacznie lepsze od dowolnego przełącznika opartego na obudowie lub tablicy, ponieważ nie powiela nazw, co ułatwia zmianę wyliczenia.
Julien Guertault

2
@ ikku100 mylisz się co do #define X(a, b) #b. Jest to konieczne tylko wtedy, gdy definicja wygląda tak X(Red, red), a nie definicja pokazana w odpowiedzi,X(Red, "red")
dowiedz się vst

43

@hydroo: Bez dodatkowego pliku:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)

#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
    SOME_ENUM(MAKE_ENUM)
};

#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
    SOME_ENUM(MAKE_STRINGS)
};

Uwielbiam to rozwiązanie. Byłoby jaśniejsze, gdyby SOME_UNION i MAKE_UNION nazywały się SOME_ENUM i MAKE_ENUM.
Bruno Martinez,

To świetne rozwiązanie. Mam najłatwiejszego w utrzymaniu menedżera zasobów C ++, z jakim kiedykolwiek miałem do czynienia.
DCurro,

Muszę ci podziękować za to proste rozwiązanie :-) - trochę go jednak zmodyfikowałem, aby MetaSyntacticVariableNames[]być częścią deklaracji klasy, tworząc metodęstatic const char* getNameByEnum(MetaSyntacticVariable e) { /*code to return the static string*/ }
DeckerDK

Fantastyczna odpowiedź! Uprościłem to dalej, grupując MAKE_ENUM i MAKE_STRINGS w jedno makro, dzięki czemu cały proces jest jeszcze prostszy. Dodałem odpowiedź w tym wątku z tym kodem, jeśli ktoś jest zainteresowany.
Francois Bertrand,

35

Zwykle tworzę tablicę C z nazwami w tej samej kolejności i pozycji, co wartości wyliczenia.

na przykład.

enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };

wtedy możesz użyć tablicy w miejscach, w których potrzebujesz wartości czytelnej dla człowieka, np

colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];

Możesz trochę poeksperymentować z operatorem stringizing (patrz # w Twoim opisie preprocesora), który zrobi to, co chcesz, w pewnych okolicznościach - np .:

#define printword(XX) cout << #XX;
printword(red);

wypisze "czerwony" na standardowe wyjście. Niestety to nie zadziała dla zmiennej (ponieważ otrzymasz wydrukowaną nazwę zmiennej)


Ostatnie zastrzeżenie (nie zadziała dla zmiennej) to duża wada, ale i tak +1.
chappjc

3
Działa tylko wtedy, gdy nie ustawisz specjalnych wartości liczbowych dla wpisów wyliczeniowych.
kyb

11

Mam niezwykle proste w użyciu makro, które robi to w całkowicie SUCHY sposób. Obejmuje różnorodne makra i prostą magię parsowania. Tutaj idzie:

#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ć tego w swoim kodzie, po prostu wykonaj:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);

1
Dobry pomysł na użycie silnie wpisanego wyliczenia (klasy wyliczenia). Oto demo: cpp.sh/4ife
chappjc

Czy to działa z zewnętrznie zdefiniowanymi wyliczeniami / symbolami. Na przykład symbole zdefiniowane przez system operacyjny lub symbole zdefiniowane w bibliotece z lukami w numeracji?
Jason Harrison

Bardzo ładnie, ale nie kompiluje się, jeśli jest umieszczony w klasie (nie mogłem zrozumieć, dlaczego).
AlwaysLearning

Nie mogłem tego skompilować w VS2015. Otrzymuję ostrzeżenie i błąd: ostrzeżenie: komentarz wielowierszowy [-Wcomment] #define MAKE_ENUM (nazwa, ...) nazwa klasy wyliczenia { VA_ARGS , __COUNT} błąd: zbłąkany '#' w standardowym programie *: string enumName = #name
Craig.Feied

8

QT jest w stanie wyciągnąć to z (dzięki kompilatorowi meta obiektu):

QNetworkReply::NetworkError error;

error = fetchStuff();

if (error != QNetworkReply::NoError) {

    QString errorValue;

    QMetaObject meta = QNetworkReply::staticMetaObject;

    for (int i=0; i < meta.enumeratorCount(); ++i) {

        QMetaEnum m = meta.enumerator(i);

        if (m.name() == QLatin1String("NetworkError")) {

            errorValue = QLatin1String(m.valueToKey(error));

            break;

        }

    }

    QMessageBox box(QMessageBox::Information, "Failed to fetch",

                "Fetching stuff failed with error '%1`").arg(errorValue),

                QMessageBox::Ok);

    box.exec();

    return 1;

}

W Qt każda klasa, która ma makro Q_OBJECT, będzie miała automatycznie statyczny element członkowski „staticMetaObject” typu QMetaObject. Następnie możesz znaleźć różne fajne rzeczy, takie jak właściwości, sygnały, szczeliny i rzeczywiście wyliczenia.

Źródło


7

Można to zrobić w C ++ 11

#include <map>
enum MyEnum { AA, BB, CC, DD };

static std::map< MyEnum, const char * > info = {
   {AA, "This is an apple"},
   {BB, "This is a book"},
   {CC, "This is a coffee"},
   {DD, "This is a door"}
};

void main()
{
    std::cout << info[AA] << endl
              << info[BB] << endl
              << info[CC] << endl
              << info[DD] << endl;
}

1
To nie odpowiada na pytanie OP: szukał sposobu na automatyczne wygenerowanie funkcji zwracającej nazwę członka wyliczenia jako ciąg.
Spooky

7

Właśnie dzisiaj ponownie wymyśliłem to koło i pomyślałem, że się nim podzielę.

Ta implementacja nie wymaga żadnych zmian w kodzie, który definiuje stałe, którymi mogą być wyliczenia, #defines lub cokolwiek innego, co przechodzi do liczby całkowitej - w moim przypadku miałem symbole zdefiniowane w kategoriach innych symboli. Działa również dobrze z rzadkimi wartościami. Pozwala nawet na wiele nazw dla tej samej wartości, zwracając zawsze pierwszą. Jedynym minusem jest to, że wymaga to utworzenia tabeli stałych, które mogą stać się nieaktualne, gdy na przykład zostaną dodane nowe.

struct IdAndName
{
   int          id;
   const char * name;
   bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }

const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
   if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
      std::stable_sort(table_begin, table_end);

   IdAndName searchee = { id, NULL };
   IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
   return (p == table_end || p->id != id) ? NULL : p->name;
}

template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
   return IdToName(id, &table[0], &table[N]);
}

Przykład tego, jak go użyjesz:

static IdAndName WindowsErrorTable[] =
{
   ID_AND_NAME(INT_MAX),               // flag value to indicate unsorted table
   ID_AND_NAME(NO_ERROR),
   ID_AND_NAME(ERROR_INVALID_FUNCTION),
   ID_AND_NAME(ERROR_FILE_NOT_FOUND),
   ID_AND_NAME(ERROR_PATH_NOT_FOUND),
   ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
   ID_AND_NAME(ERROR_ACCESS_DENIED),
   ID_AND_NAME(ERROR_INVALID_HANDLE),
   ID_AND_NAME(ERROR_ARENA_TRASHED),
   ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
   ID_AND_NAME(ERROR_INVALID_BLOCK),
   ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
   ID_AND_NAME(ERROR_BAD_FORMAT),
   ID_AND_NAME(ERROR_INVALID_ACCESS),
   ID_AND_NAME(ERROR_INVALID_DATA),
   ID_AND_NAME(ERROR_INVALID_DRIVE),
   ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
   ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
   ID_AND_NAME(ERROR_NO_MORE_FILES)
};

const char * error_name = IdToName(GetLastError(), WindowsErrorTable);

IdToNameFunkcja polega na std::lower_boundrobić szybkich wyszukiwań, co wymaga tabelę być posortowane. Jeśli pierwsze dwa wpisy w tabeli nie są w kolejności, funkcja posortuje je automatycznie.

Edycja: komentarz sprawił, że pomyślałem o innym sposobie wykorzystania tej samej zasady. Makro upraszcza generowanie dużej switchinstrukcji.

#define ID_AND_NAME(x) case x: return #x

const char * WindowsErrorToName(int id)
{
    switch(id)
    {
        ID_AND_NAME(ERROR_INVALID_FUNCTION);
        ID_AND_NAME(ERROR_FILE_NOT_FOUND);
        ID_AND_NAME(ERROR_PATH_NOT_FOUND);
        ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
        ID_AND_NAME(ERROR_ACCESS_DENIED);
        ID_AND_NAME(ERROR_INVALID_HANDLE);
        ID_AND_NAME(ERROR_ARENA_TRASHED);
        ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
        ID_AND_NAME(ERROR_INVALID_BLOCK);
        ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
        ID_AND_NAME(ERROR_BAD_FORMAT);
        ID_AND_NAME(ERROR_INVALID_ACCESS);
        ID_AND_NAME(ERROR_INVALID_DATA);
        ID_AND_NAME(ERROR_INVALID_DRIVE);
        ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
        ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
        ID_AND_NAME(ERROR_NO_MORE_FILES);
        default: return NULL;
    }
}

Dobre rozwiązanie. Ale dla mnie wolałbym, switch and caseponieważ jest to proste i łatwe do zrozumienia.
Deqing

6
#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Dalsza dyskusja na temat tej metody

Triki z dyrektywami preprocesora dla nowych użytkowników


4
W rzeczywistości jest to dość bezużyteczne, ponieważ metoda stringify jest w czasie kompilacji i jest dość dosłowna. Jeśli powiesz, że dany typ wyliczenia znajduje się w zmiennej, próba zdefiniowania zmiennej poda po prostu nazwę zmiennej, a nie nazwę typu wyliczenia.
srcspider

5

Ciekawe, aby zobaczyć, ile sposobów. oto jeden, którego użyłem dawno temu:

w pliku myenummap.h:

#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
  mymap()
  {
    this->operator[]( one ) = "ONE";
    this->operator[]( two ) = "TWO";
    this->operator[]( three ) = "THREE";
    this->operator[]( five ) = "FIVE";
    this->operator[]( six ) = "SIX";
    this->operator[]( seven ) = "SEVEN";
  };
  ~mymap(){};
};

w main.cpp

#include "myenummap.h"

...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;

To nie jest stała, ale wygodna.

Oto inny sposób korzystania z funkcji C ++ 11. To jest const, nie dziedziczy kontenera STL i jest trochę bardziej uporządkowane:

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

//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
    typedef std::pair<int,std::string> mapping;
    auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);}; 
    std::vector<mapping> const nummap = 
    { 
        m(one,"one"), 
        m(two,"two"), 
        m(three,"three"),
        m(five,"five"),
        m(six,"six"),
        m(seven,"seven"),
    };
    for(auto i  : nummap)
    {
        if(i.first==static_cast<int>(e))
        {
            return i.second;
        }
    }
    return "";
}

int main()
{
//  std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
    std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
    std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
    return 0;
}

1
Jest to całkowicie legalne. Ciągle to robię.
Jonathan Graehl,

Dobre rozwiązanie. To jest c ++, więc używanie mapy stl jest w porządku.
Adam Bruss

4
#include <stdarg.h>
#include <algorithm>
#include <string> 
#include <vector>
#include <sstream>
#include <map>

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
private:                                                            \
    static std::map<int, std::string> nameMap;                      \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static std::map<int, std::string> initMap()                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        map<int, string> tmp;                                       \
        for(vector<string>::iterator it = strings.begin();          \
                                               it != strings.end(); \
                                               ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
        return tmp;                                                 \
    }                                                               \
public:                                                             \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap[aInt];                                       \
    }                                                               \
};                                                                  \
std::map<int, std::string>                                          \
EnumName::nameMap = EnumName::initMap();

Stosowanie:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);

1
Podoba mi się twoje API, ale niestety, twój SmartEnum tak naprawdę nie tworzy "typu" wyliczenia. Nie możesz tego zrobić MyEnum x = MyEnum::TWO;. Opublikowałem swoją edycję Twojej klasy, aby to wesprzeć.
Mark Lakata

4

Rozwiązanie makro Sumy jest fajne. Nie musisz jednak mieć dwóch różnych makr. C ++ na szczęście dwa razy dołączy nagłówek. Po prostu pomiń osłonę włączającą.

Więc miałbyś foobar.h definiujący tylko

ENUM(Foo, 1)
ENUM(Bar, 2)

i umieściłbyś to w ten sposób:

#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"

enumfactory.h zrobi 2 #include ENUMFACTORY_ARGUMENT s. W pierwszej rundzie ENUM rozszerza się jak suma DECLARE_ENUM; w drugiej rundzie ENUM działa jak DEFINE_ENUM.

Możesz dołączyć enumfactory.h również wiele razy, o ile przekazujesz różne # define's dla ENUMFACTORY_ARGUMENT


wydaje się, że suma przeniosła tutaj odpowiedź . Możesz dołączyć link do swojej odpowiedzi. Komentarz znalazłem tylko przez przypadek i bez odpowiedzi suma ten jest raczej bezcelowy
idclev 463035818

3

Zwróć uwagę, że najlepiej byłoby, gdyby funkcja konwersji zwracała const char *.

Jeśli możesz sobie pozwolić na umieszczanie wyliczeń w oddzielnych plikach nagłówkowych, możesz zrobić coś takiego z makrami (o, to będzie brzydkie):

#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"

Gdzie enum_def.h ma:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };

A enum_conv.h ma:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }

I wreszcie colour.h ma:

ENUM_START(colour)
ENUM_ADD(red,   0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue,  0x0000ff)
ENUM_END

I możesz użyć funkcji konwersji jako:

printf("%s", colour_to_string(colour::red));

To jest brzydkie, ale jest to jedyny sposób (na poziomie preprocesora), który pozwala zdefiniować wyliczenie tylko w jednym miejscu w kodzie. Twój kod nie jest zatem podatny na błędy spowodowane modyfikacjami wyliczenia. Twoja definicja wyliczenia i funkcja konwersji będą zawsze zsynchronizowane. Jednak powtarzam, to jest brzydkie :)


3

Inna odpowiedź: w niektórych kontekstach sensowne jest zdefiniowanie wyliczenia w formacie niekodowym, takim jak plik CSV, YAML lub XML, a następnie wygenerowanie zarówno kodu wyliczenia C ++, jak i kodu ciągu na podstawie definicji. Takie podejście może być praktyczne w Twojej aplikacji lub nie, ale warto o tym pamiętać.


3

To jest modyfikacja odpowiedzi @ user3360260. Posiada następujące nowe funkcje

  • MyEnum fromString(const string&) wsparcie
  • kompiluje się z VisualStudio 2012
  • wyliczenie jest rzeczywistym typem POD (nie tylko deklaracjami const), więc możesz przypisać go do zmiennej.
  • dodano funkcję „zakresu” C ++ (w postaci wektora), aby umożliwić iterację „foreach” po wyliczeniu

Stosowanie:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo);  // static method
cout << foo.toString();         // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");

// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
  cout << x.toString() << endl;
}

Oto kod

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
public:                                                             \
    EnumName() : value(0) {}                                        \
    EnumName(int x) : value(x) {}                                   \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static void initMap(std::map<int, std::string>& tmp)                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        for(vector<string>::iterator it = strings.begin();          \
                                                it != strings.end(); \
                                                ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
    }                                                               \
    int value;                                                      \
public:                                                             \
    operator int () const { return value; }                         \
    std::string toString(void) const {                              \
            return toString(value);                                 \
    }                                                               \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap()[aInt];                                     \
    }                                                               \
    static EnumName fromString(const std::string& s)                \
    {                                                               \
        auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
            return p.second == s;                                   \
        });                                                         \
        if (it == nameMap().end()) {                                \
        /*value not found*/                                         \
            throw EnumName::Exception();                            \
        } else {                                                    \
            return EnumName(it->first);                             \
        }                                                           \
    }                                                               \
    class Exception : public std::exception {};                     \
    static std::map<int,std::string>& nameMap() {                   \
      static std::map<int,std::string> nameMap0;                    \
      if (nameMap0.size() ==0) initMap(nameMap0);                   \
      return nameMap0;                                              \
    }                                                               \
    static std::vector<EnumName> allValues() {                      \
      std::vector<EnumName> x{ __VA_ARGS__ };                       \
      return x;                                                     \
    }                                                               \
    bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};         

Zauważ, że konwersja toString to szybkie wyszukiwanie, podczas gdy konwersja fromString to powolne wyszukiwanie liniowe. Ale łańcuchy i tak są tak drogie (i związane z nimi IO), że nie czułem potrzeby optymalizacji lub używania bimapy.


Ty i użytkownik3360260 macie dobre rozwiązanie. Dlaczego zamiast tego nie mieć multimapy?
Vincent

3

Tutaj rozwiązanie jednoplikowe (oparte na eleganckiej odpowiedzi @Marcin:

#include <iostream>

#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \

enum Colours {
#   define X(a) a,
ENUM_TXT
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
ENUM_TXT
#   undef X
    0
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c] << std::endl;
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

2

Robię to z oddzielnymi klasami opakowującymi wyliczenia obok siebie, które są generowane za pomocą makr. Jest kilka zalet:

  • Można je wygenerować dla wyliczeń, których nie definiuję (np .: wyliczenia nagłówka platformy systemu operacyjnego)
  • Może zawierać sprawdzanie zakresu w klasie opakowania
  • Potrafi „inteligentniej” formatować za pomocą wyliczeń pól bitowych

Wadą jest oczywiście to, że muszę zduplikować wartości wyliczenia w klasach programu formatującego i nie mam żadnego skryptu do ich generowania. Poza tym wydaje się, że działa całkiem nieźle.

Oto przykład wyliczenia z mojej bazy kodu, bez całego kodu frameworka, który implementuje makra i szablony, ale możesz zrozumieć:

enum EHelpLocation
{
    HELP_LOCATION_UNKNOWN   = 0, 
    HELP_LOCAL_FILE         = 1, 
    HELP_HTML_ONLINE        = 2, 
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
    static inline CString FormatEnum( EHelpLocation eValue )
    {
        switch ( eValue )
        {
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
        default:
            return FormatAsNumber( eValue );
        }
    }
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;

Pomysł jest więc taki, że zamiast używać EHelpLocation, używasz SEHelpLocation; wszystko działa tak samo, ale otrzymujesz sprawdzanie zakresu i metodę „Format ()” na samej zmiennej wyliczeniowej. Jeśli chcesz sformatować wartość autonomiczną, możesz użyć CEnumFormatter_EHelpLocation :: FormatEnum (...).

Mam nadzieję, że to jest pomocne. Zdaję sobie sprawę, że to również nie odnosi się do pierwotnego pytania o skrypt, który faktycznie generuje drugą klasę, ale mam nadzieję, że struktura pomoże komuś próbującemu rozwiązać ten sam problem lub napisać taki skrypt.


2

To niewydane oprogramowanie, ale wydaje się, że BOOST_ENUM autorstwa Franka Lauba może pasować do rachunku. Podoba mi się to, że można zdefiniować wyliczenie w zakresie klasy, na co większość wyliczeń opartych na makrach zwykle nie pozwala. Znajduje się w Boost Vault pod adresem : http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& Nie widziałem żadnych zmian od 2006 roku, więc nie wiedzieć, jak dobrze komponuje się z nowymi wydaniami Boost. Przykład użycia znajdziesz w libs / test.


2

To było moje rozwiązanie z BOOST:

#include <boost/preprocessor.hpp>

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

#define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
    else if(data == BOOST_PP_STRINGIZE(elem)) return elem;

#define STR_ENUM(name, enumerators)                                             \
    enum name {                                                                 \
        BOOST_PP_SEQ_ENUM(enumerators)                                          \
    };                                                                          \
                                                                                \
    inline const QString enumToStr(name v)                                      \
    {                                                                           \
        switch (v)                                                              \
        {                                                                       \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                X_STR_ENUM_TOSTRING_CASE,                                       \
                name,                                                           \
                enumerators                                                     \
            )                                                                   \
                                                                                \
            default:                                                            \
                return "[Unknown " BOOST_PP_STRINGIZE(name) "]";                \
        }                                                                       \
    }                                                                           \
                                                                                \
    template <typename T>                                                       \
    inline const T strToEnum(QString v);                                        \
                                                                                \
    template <>                                                                 \
    inline const name strToEnum(QString v)                                      \
    {                                                                           \
        if(v=="")                                                               \
            throw std::runtime_error("Empty enum value");                       \
                                                                                \
        BOOST_PP_SEQ_FOR_EACH(                                                  \
            X_ENUM_STR_TOENUM_IF,                                               \
            v,                                                                  \
            enumerators                                                         \
        )                                                                       \
                                                                                \
        else                                                                    \
            throw std::runtime_error(                                           \
                        QString("[Unknown value %1 for enum %2]")               \
                            .arg(v)                                             \
                            .arg(BOOST_PP_STRINGIZE(name))                      \
                                .toStdString().c_str());                        \
    }

Aby utworzyć wyliczenie, zadeklaruj:

STR_ENUM
(
    SERVICE_RELOAD,
        (reload_log)
        (reload_settings)
        (reload_qxml_server)
)

W przypadku konwersji:

SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);

2

Chcę to opublikować na wypadek, gdyby ktoś uznał to za przydatne.

W moim przypadku po prostu muszę wygenerować ToString()i FromString()działać dla pojedynczego wyliczenia C ++ 11 z jednego .hpppliku.

Napisałem skrypt w języku Python, który analizuje plik nagłówkowy zawierający elementy wyliczenia i generuje funkcje w nowym .cpppliku.

Możesz dodać ten skrypt w CMakeLists.txt z execute_process lub jako zdarzenie przed kompilacją w programie Visual Studio. .cppPlik zostanie automatycznie wygenerowany, bez konieczności ręcznie aktualizować za każdym razem nowy element wyliczenia dodaje.

gene_enum_strings.py

# This script is used to generate strings from C++ enums

import re
import sys
import os

fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])

with open(fileName, 'r') as f:
    content = f.read().replace('\n', '')

searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)

textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += '    std::string ToString(ErrorCode errorCode)\n'
textOut += '    {\n'
textOut += '        switch (errorCode)\n'
textOut += '        {\n'

for token in tokens:
    textOut += '        case ' + enumName + '::' + token + ':\n'
    textOut += '            return "' + token + '";\n'

textOut += '        default:\n'
textOut += '            return "Last";\n'
textOut += '        }\n'
textOut += '    }\n'
textOut += '\n'
textOut += '    ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += '    {\n'
textOut += '        if ("' + tokens[0] + '" == errorCode)\n'
textOut += '        {\n'
textOut += '            return ' + enumName + '::' + tokens[0] + ';\n'
textOut += '        }\n'

for token in tokens[1:]:
    textOut += '        else if("' + token + '" == errorCode)\n'
    textOut += '        {\n'
    textOut += '            return ' + enumName + '::' + token + ';\n'
    textOut += '        }\n'

textOut += '\n'
textOut += '        return ' + enumName + '::Last;\n'
textOut += '    }\n'
textOut += '}\n'

fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)

Przykład:

ErrorCode.hpp

#pragma once

#include <string>
#include <cstdint>

namespace myns
{
    enum class ErrorCode : uint32_t
    {
        OK = 0,
        OutOfSpace,
        ConnectionFailure,
        InvalidJson,
        DatabaseFailure,
        HttpError,
        FileSystemError,
        FailedToEncrypt,
        FailedToDecrypt,
        EndOfFile,
        FailedToOpenFileForRead,
        FailedToOpenFileForWrite,
        FailedToLaunchProcess,

        Last
    };

    std::string ToString(ErrorCode errorCode);
    ErrorCode FromString(const std::string &errorCode);
}

Biegać python generate_enum_strings.py ErrorCode.hpp

Wynik:

ErrorCode.cpp

#include "ErrorCode.hpp"

namespace myns
{
    std::string ToString(ErrorCode errorCode)
    {
        switch (errorCode)
        {
        case ErrorCode::OK:
            return "OK";
        case ErrorCode::OutOfSpace:
            return "OutOfSpace";
        case ErrorCode::ConnectionFailure:
            return "ConnectionFailure";
        case ErrorCode::InvalidJson:
            return "InvalidJson";
        case ErrorCode::DatabaseFailure:
            return "DatabaseFailure";
        case ErrorCode::HttpError:
            return "HttpError";
        case ErrorCode::FileSystemError:
            return "FileSystemError";
        case ErrorCode::FailedToEncrypt:
            return "FailedToEncrypt";
        case ErrorCode::FailedToDecrypt:
            return "FailedToDecrypt";
        case ErrorCode::EndOfFile:
            return "EndOfFile";
        case ErrorCode::FailedToOpenFileForRead:
            return "FailedToOpenFileForRead";
        case ErrorCode::FailedToOpenFileForWrite:
            return "FailedToOpenFileForWrite";
        case ErrorCode::FailedToLaunchProcess:
            return "FailedToLaunchProcess";
        case ErrorCode::Last:
            return "Last";
        default:
            return "Last";
        }
    }

    ErrorCode FromString(const std::string &errorCode)
    {
        if ("OK" == errorCode)
        {
            return ErrorCode::OK;
        }
        else if("OutOfSpace" == errorCode)
        {
            return ErrorCode::OutOfSpace;
        }
        else if("ConnectionFailure" == errorCode)
        {
            return ErrorCode::ConnectionFailure;
        }
        else if("InvalidJson" == errorCode)
        {
            return ErrorCode::InvalidJson;
        }
        else if("DatabaseFailure" == errorCode)
        {
            return ErrorCode::DatabaseFailure;
        }
        else if("HttpError" == errorCode)
        {
            return ErrorCode::HttpError;
        }
        else if("FileSystemError" == errorCode)
        {
            return ErrorCode::FileSystemError;
        }
        else if("FailedToEncrypt" == errorCode)
        {
            return ErrorCode::FailedToEncrypt;
        }
        else if("FailedToDecrypt" == errorCode)
        {
            return ErrorCode::FailedToDecrypt;
        }
        else if("EndOfFile" == errorCode)
        {
            return ErrorCode::EndOfFile;
        }
        else if("FailedToOpenFileForRead" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForRead;
        }
        else if("FailedToOpenFileForWrite" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForWrite;
        }
        else if("FailedToLaunchProcess" == errorCode)
        {
            return ErrorCode::FailedToLaunchProcess;
        }
        else if("Last" == errorCode)
        {
            return ErrorCode::Last;
        }

        return ErrorCode::Last;
    }
}


2

Dodając jeszcze prostszą obsługę do fantastycznej odpowiedzi Jaspera Bekkersa :

Skonfiguruj raz:

#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
    enum enumName { \
    source(MAKE_ENUM) \
    };\
const char* const enumStringName[] = { \
    source(MAKE_STRINGS) \
    };

Następnie do użytku:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)

2

Możesz użyć biblioteki refleksji, takiej jak Ponder . Rejestrujesz wyliczenia, a następnie konwertujesz je tam iz powrotem za pomocą interfejsu API.

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"

1

Problem z odpowiedzią 0 polega na tym, że wartości binarne wyliczenia niekoniecznie zaczynają się od 0 i niekoniecznie są ciągłe.

Kiedy tego potrzebuję, zwykle:

  • ściągnij definicję wyliczenia do mojego źródła
  • edytuj go, aby uzyskać tylko nazwy
  • zrób makro, aby zmienić nazwę na klauzulę case w pytaniu, zwykle w jednym wierszu: case foo: return "foo";
  • dodaj przełącznik, domyślną i inną składnię, aby była legalna

1

Poniższy skrypt ruby ​​próbuje przeanalizować nagłówki i zbudować wymagane źródła obok oryginalnych nagłówków.

#! /usr/bin/env ruby

# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs

GLOBS = [
  "toto/*.h",
  "tutu/*.h",
  "tutu/*.hxx"
]

enums = {}
GLOBS.each { |glob|
  Dir[glob].each { |header|
    enums[header] = File.open(header, 'rb') { |f|
      f.read
    }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
      [
        enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
          enum_key_and_value.split(/\s*=\s*/).first
        }
      ]
    }
  }
}


# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'

template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1

#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);

#endif
EOS

template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"

char* enum_to_string(<%= enum_name %> e)
{
  switch (e)
  {<% enum_keys.each do |enum_key| %>
    case <%= enum_key %>: return "<%= enum_key %>";<% end %>
    default: return "INVALID <%= enum_name %> VALUE";
  }
}
EOS

enums.each { |header, enum_name_and_keys|
  enum_name_and_keys.each { |enum_name, enum_keys|
    File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
      built_h.write(template_h.result(binding))
    }

    File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
      built_cpp.write(template_cpp.result(binding))
    }
  }
}

Używanie wyrażeń regularnych sprawia, że ​​ten "parser" jest dość kruchy, może nie być w stanie z wdziękiem obsłużyć określonych nagłówków.

Powiedzmy, że masz nagłówek toto / ah, zawierający definicje wyliczeń MyEnum i MyEnum2. Skrypt zbuduje:

toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp

Bardziej solidne rozwiązania to:

  • Zbuduj wszystkie źródła definiujące wyliczenia i ich operacje z innego źródła. Oznacza to, że zdefiniujesz wyliczenia w pliku XML / YML / innym, który jest znacznie łatwiejszy do przeanalizowania niż C / C ++.
  • Użyj prawdziwego kompilatora, takiego jak sugerowany przez Avdi.
  • Użyj makr preprocesora z szablonami lub bez.

0

To właściwie jedyny sposób, w jaki można to zrobić (tablica ciągów może również działać).

Problem polega na tym, że po skompilowaniu programu w języku C wartość binarna wyliczenia jest wszystkim, co jest używane, a nazwa znika.


0

Oto program CLI, który napisałem, aby łatwo konwertować wyliczenia na ciągi. Jest łatwy w użyciu i zajmuje około 5 sekund, aby to zrobić (łącznie z czasem cd do katalogu zawierającego program, a następnie uruchom go, przekazując do niego plik zawierający wyliczenie).

Pobierz tutaj: http://www.mediafire.com/?nttignoozzz

Temat dyskusji na ten temat tutaj: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Uruchom program z argumentem „--help”, aby uzyskać opis, jak go używać.


Czy mógłbyś umieścić to gdzieś w repozytorium (github, kod google lub bitbucket) i zamieścić tutaj link zamiast mediafire? Pomogę ludziom, którzy chcą to zrozumieć :)
Edu Felipe

0

Nie tak dawno zrobiłem sztuczkę, aby wyliczenia były poprawnie wyświetlane w QComboBox i aby definicja reprezentacji wyliczeń i ciągów była jedną instrukcją

#pragma once
#include <boost/unordered_map.hpp>

namespace enumeration
{

   struct enumerator_base : boost::noncopyable
   {
      typedef
         boost::unordered_map<int, std::wstring>
         kv_storage_t;
      typedef
         kv_storage_t::value_type
         kv_type;
      kv_storage_t const & kv() const
      {
         return storage_;
      }

      LPCWSTR name(int i) const
      {
         kv_storage_t::const_iterator it = storage_.find(i);
         if(it != storage_.end())
            return it->second.c_str();
         return L"empty";
      }

   protected:
      kv_storage_t storage_;
   };

   template<class T>
   struct enumerator;

   template<class D>
   struct enum_singleton : enumerator_base
   {
      static enumerator_base const & instance()
      {
         static D inst;
         return inst;
      }
   };
}

#define QENUM_ENTRY(K, V, N)  K, N storage_.insert(std::make_pair((int)K, V));

#define QBEGIN_ENUM(NAME, C)   \
enum NAME                     \
{                             \
   C                          \
}                             \
};                            \
}                             \

#define QEND_ENUM(NAME) \
};                     \
namespace enumeration  \
{                      \
template<>             \
struct enumerator<NAME>\
   : enum_singleton< enumerator<NAME> >\
{                      \
   enumerator()        \
   {

//usage
/*
QBEGIN_ENUM(test_t,
   QENUM_ENTRY(test_entry_1, L"number uno",
   QENUM_ENTRY(test_entry_2, L"number dos",
   QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/

Teraz możesz enumeration::enum_singleton<your_enum>::instance()konwertować wyliczenia na ciągi. Jeśli zastąpisz kv_storage_tgo boost::bimap, będziesz mógł również wykonać konwersję wsteczną. Wprowadzono wspólną klasę bazową dla konwertera, aby przechowywać go w obiekcie Qt, ponieważ obiekty Qt nie mogą być szablonami

Poprzedni wygląd


0

Jako wariant użyj prostej biblioteki> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C

W kodzie

#include <EnumString.h>

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

dodaj linie

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

Działa dobrze, jeśli wartości w enum nie są publikowane .

Przykładowe użycie

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

i wzajemnie

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

0

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!


0
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)

std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
    mapEnumtoString(){  }
    mapEnumtoString& operator()(int i,std::string str)
    {
        enToStr[i] = str;
        return *this;
    }
public:
   std::string operator [] (int i)
    {
        return enToStr[i];
    }

};
mapEnumtoString k;
mapEnumtoString& init()
{
    return k;
}

int main()
{

init()
    IDMAP(1)
    IDMAP(2)
    IDMAP(3)
    IDMAP(4)
    IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}

2
Prosimy o wyjaśnienie, dlaczego to jest odpowiedź.
Alok
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.