Oto, co próbuję zrobić:
typedef enum { ONE, TWO, THREE } Numbers;
Próbuję napisać funkcję, która zrobiłaby przypadek przełącznika podobny do następującego:
char num_str[10];
int process_numbers_str(Numbers num) {
switch(num) {
case ONE:
case TWO:
case THREE:
{
strcpy(num_str, num); //some way to get the symbolic constant name in here?
} break;
default:
return 0; //no match
return 1;
}
Zamiast definiować w każdym przypadku, czy istnieje sposób na ustawienie go za pomocą zmiennej wyliczeniowej, tak jak próbuję to zrobić powyżej?
c
enums
c-preprocessor
zxcv
źródło
źródło
Technika z tworzenia czegoś zarówno identyfikatora C, jak i łańcucha? można użyć tutaj.
Jak zwykle w przypadku takich preprocesorów, pisanie i rozumienie części preprocesora może być trudne i obejmuje przekazywanie makr do innych makr i wymaga użycia operatorów # i ##, ale korzystanie z tego jest naprawdę łatwe. Uważam, że ten styl jest bardzo przydatny w przypadku długich wyliczeń, gdzie dwukrotne utrzymywanie tej samej listy może być naprawdę kłopotliwe.
Kod fabryczny - wpisywany tylko raz, zwykle ukryty w nagłówku:
enumFactory.h:
// expansion macro for enum value definition #define ENUM_VALUE(name,assign) name assign, // expansion macro for enum to string conversion #define ENUM_CASE(name,assign) case name: return #name; // expansion macro for string to enum conversion #define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name; /// declare the access function and define enum values #define DECLARE_ENUM(EnumType,ENUM_DEF) \ enum EnumType { \ ENUM_DEF(ENUM_VALUE) \ }; \ const char *GetString(EnumType dummy); \ EnumType Get##EnumType##Value(const char *string); \ /// define the access function names #define DEFINE_ENUM(EnumType,ENUM_DEF) \ const char *GetString(EnumType value) \ { \ switch(value) \ { \ ENUM_DEF(ENUM_CASE) \ default: return ""; /* handle input error */ \ } \ } \ EnumType Get##EnumType##Value(const char *str) \ { \ ENUM_DEF(ENUM_STRCMP) \ return (EnumType)0; /* handle input error */ \ } \
Przemysłowy użytek
someEnum.h:
#include "enumFactory.h" #define SOME_ENUM(XX) \ XX(FirstValue,) \ XX(SecondValue,) \ XX(SomeOtherValue,=50) \ XX(OneMoreValue,=100) \ DECLARE_ENUM(SomeEnum,SOME_ENUM)
someEnum.cpp:
#include "someEnum.h" DEFINE_ENUM(SomeEnum,SOME_ENUM)
Technikę można łatwo rozszerzyć, aby makra XX przyjmowały więcej argumentów, a także można przygotować więcej makr zastępujących XX dla różnych potrzeb, podobnie jak trzy, które podałem w tym przykładzie.
Porównanie z X-Macros przy użyciu #include / #define / #undef
Chociaż jest to podobne do X-Macros, o których wspominali inni, myślę, że to rozwiązanie jest bardziej eleganckie, ponieważ nie wymaga niczego #undefing, co pozwala ukryć więcej skomplikowanych rzeczy w fabryce plik nagłówkowy - plik nagłówkowy jest czymś, czego w ogóle nie dotykasz, gdy musisz zdefiniować nowe wyliczenie, dlatego nowa definicja wyliczenia jest znacznie krótsza i bardziej przejrzysta.
źródło
SOME_ENUM(XX)
to dokładnie X-makro (a dokładniej „formularz użytkownika”, który przekazujeXX
funkcję, a nie używa#def
#undef
), a następnie całe X-MACRO jest następnie przekazywane do DEFINE_ENUM, które go używa. Nie odejmować niczego od rozwiązania - działa dobrze. Wystarczy wyjaśnić, że jest to użycie makr X.XX
pomijania#define
reing jest to, że poprzedni wzorzec może być użyty w ekspansjach makro. Zwróć uwagę, że jedyne inne rozwiązania tak zwięzłe, jak to, wymagają utworzenia i wielokrotnego włączenia oddzielnego pliku w celu zdefiniowania nowego wyliczenia.#define DEFINE_ENUM(EnumType) ...
, wymienićENUM_DEF(...)
sięEnumType(...)
i mają głosu użytkownika#define SomeEnum(XX) ...
. Preprocesor C kontekstowo rozwinieSomeEnum
się do wywołania makra, jeśli będzie poprzedzony nawiasami, aw przeciwnym razie do zwykłego tokenu. (Oczywiście powoduje to problemy, jeśli użytkownik lubiSomeEnum(2)
rzutować na typ wyliczeniowy zamiast(SomeEnum)2
lubstatic_cast<SomeEnum>(2)
.)#define
i#undef
. Czy nie zgadzasz się jednak z tym, że „formularz użytkownika” (sugerowany np. Na dole tego artykułu ) jest rodzajem makra x? Z pewnością zawsze nazywałam to również makro x, aw bazach kodu C, w których ostatnio byłem, jest to najbardziej powszechna forma (jest to oczywiście tendencyjna obserwacja). Być może źle analizowałem OP.// Define your enumeration like this (in say numbers.h); ENUM_BEGIN( Numbers ) ENUM(ONE), ENUM(TWO), ENUM(FOUR) ENUM_END( Numbers ) // The macros are defined in a more fundamental .h file (say defs.h); #define ENUM_BEGIN(typ) enum typ { #define ENUM(nam) nam #define ENUM_END(typ) }; // Now in one and only one .c file, redefine the ENUM macros and reinclude // the numbers.h file to build a string table #undef ENUM_BEGIN #undef ENUM #undef ENUM_END #define ENUM_BEGIN(typ) const char * typ ## _name_table [] = { #define ENUM(nam) #nam #define ENUM_END(typ) }; #undef NUMBERS_H_INCLUDED // whatever you need to do to enable reinclusion #include "numbers.h" // Now you can do exactly what you want to do, with no retyping, and for any // number of enumerated types defined with the ENUM macro family // Your code follows; char num_str[10]; int process_numbers_str(Numbers num) { switch(num) { case ONE: case TWO: case THREE: { strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO" } break; default: return 0; //no match return 1; } // Sweet no ? After being frustrated by this for years, I finally came up // with this solution for my most recent project and plan to reuse the idea // forever
źródło
#define ENUM_END(typ) }; extern const char * typ ## _name_table[];
wdefs.h
pliku - to zadeklaruje tablicę nazw w plikach, których używasz. (Nie mogę znaleźć dobrego sposobu na zadeklarowanie rozmiaru tabeli). Osobiście zostawiłbym także ostatni średnik, ale zalety są dyskusyjne.typ
w kolejce#define ENUM_END(typ) };
?Na pewno jest na to sposób - użyj makr X () . Te makra używają preprocesora C do konstruowania wyliczeń, tablic i bloków kodu z listy danych źródłowych. Musisz tylko dodać nowe elementy do #define zawierającego makro X (). Instrukcja switch rozwijałaby się automatycznie.
Twój przykład można zapisać w następujący sposób:
// Source data -- Enum, String #define X_NUMBERS \ X(ONE, "one") \ X(TWO, "two") \ X(THREE, "three") ... // Use preprocessor to create the Enum typedef enum { #define X(Enum, String) Enum, X_NUMBERS #undef X } Numbers; ... // Use Preprocessor to expand data into switch statement cases switch(num) { #define X(Enum, String) \ case Enum: strcpy(num_str, String); break; X_NUMBERS #undef X default: return 0; break; } return 1;
Istnieją bardziej wydajne sposoby (np. Użycie X Macros do tworzenia tablicy ciągów i indeksu wyliczeniowego), ale to jest najprostsze demo.
źródło
Wiem, że masz kilka dobrych, solidnych odpowiedzi, ale czy wiesz o operatorze # w preprocesorze C?
Pozwala to zrobić:
#define MACROSTR(k) #k typedef enum { kZero, kOne, kTwo, kThree } kConst; static char *kConstStr[] = { MACROSTR(kZero), MACROSTR(kOne), MACROSTR(kTwo), MACROSTR(kThree) }; static void kConstPrinter(kConst k) { printf("%s", kConstStr[k]); }
źródło
char const *kConstStr[]
C lub C ++ nie zapewniają tej funkcjonalności, chociaż często jej potrzebowałem.
Poniższy kod działa, chociaż najlepiej nadaje się do nierzadkich wyliczeń.
typedef enum { ONE, TWO, THREE } Numbers; char *strNumbers[] = {"one","two","three"}; printf ("Value for TWO is %s\n",strNumbers[TWO]);
Mówiąc „nie rzadkie”, mam na myśli nie formę
typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;
ponieważ ma w tym ogromne luki.
Zaletą tej metody jest to, że umieszcza ona definicje wyliczeń i łańcuchów obok siebie; posiadanie instrukcji switch w funkcji powoduje ich spearowanie. Oznacza to, że jest mniej prawdopodobne, że zmienisz jedno bez drugiego.
źródło
POCAŁUNEK. Będziesz robił wiele innych rzeczy związanych z przełączaniem / przypadkami ze swoimi wyliczeniami, więc dlaczego drukowanie miałoby być inne? Zapomnienie o sprawie w rutynowych czynnościach drukowania nie jest wielkim problemem, biorąc pod uwagę około 100 innych miejsc, w których można zapomnieć o skrzynce. Po prostu skompiluj -Wall, co będzie ostrzegać o niewyczerpujących dopasowaniach wielkości liter. Nie używaj wartości „domyślnej”, ponieważ spowoduje to, że zmiana będzie wyczerpująca i nie otrzymasz ostrzeżeń. Zamiast tego pozwól przełącznikowi wyjść i zajmij się przypadkiem domyślnym w ten sposób ...
const char *myenum_str(myenum e) { switch(e) { case ONE: return "one"; case TWO: return "two"; } return "invalid"; }
źródło
Spróbuj przekonwertować wyliczenia języka C ++ na ciągi . Te komentarze mają ulepszeń, które rozwiązują ten problem, gdy enum elementy mają dowolne wartości.
źródło
Zastosowanie boost :: preprocessor umożliwia eleganckie rozwiązanie, takie jak:
Krok 1: dołącz plik nagłówkowy:
#include "EnumUtilities.h"
Krok 2: zadeklaruj obiekt wyliczenia z następującą składnią:
Krok 3: wykorzystaj swoje dane:
Uzyskanie liczby elementów:
td::cout << "Number of Elements: " << TestDataCount << std::endl;
Pobieranie powiązanego ciągu:
std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl; std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl; std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;
Pobieranie wartości wyliczenia z powiązanego ciągu:
std::cout << "Value of x is " << TestData2Enum("x") << std::endl; std::cout << "Value of y is " << TestData2Enum("y") << std::endl; std::cout << "Value of z is " << TestData2Enum("z") << std::endl;
Wygląda na czysty i kompaktowy, bez dodatkowych plików do dołączenia. Kod, który napisałem w EnumUtilities.h, jest następujący:
#include <boost/preprocessor/seq/for_each.hpp> #include <string> #define REALLY_MAKE_STRING(x) #x #define MAKE_STRING(x) REALLY_MAKE_STRING(x) #define MACRO1(r, data, elem) elem, #define MACRO1_STRING(r, data, elem) case elem: return REALLY_MAKE_STRING(elem); #define MACRO1_ENUM(r, data, elem) if (REALLY_MAKE_STRING(elem) == eStrEl) return elem; #define MakeEnum(eName, SEQ) \ enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \ last_##eName##_enum}; \ const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \ static std::string eName##2String(const enum eName eel) \ { \ switch (eel) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \ default: return "Unknown enumerator value."; \ }; \ }; \ static enum eName eName##2Enum(const std::string eStrEl) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \ return (enum eName)0; \ };
Istnieją pewne ograniczenia, np. Te z boost :: preprocessor. W tym przypadku lista stałych nie może być większa niż 64 elementy.
Postępując zgodnie z tą samą logiką, możesz również pomyśleć o utworzeniu rzadkiego wyliczenia:
#define EnumName(Tuple) BOOST_PP_TUPLE_ELEM(2, 0, Tuple) #define EnumValue(Tuple) BOOST_PP_TUPLE_ELEM(2, 1, Tuple) #define MACRO2(r, data, elem) EnumName(elem) EnumValue(elem), #define MACRO2_STRING(r, data, elem) case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem)); #define MakeEnumEx(eName, SEQ) \ enum eName { \ BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \ last_##eName##_enum }; \ const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \ static std::string eName##2String(const enum eName eel) \ { \ switch (eel) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \ default: return "Unknown enumerator value."; \ }; \ };
W tym przypadku składnia jest następująca:
MakeEnumEx(TestEnum, ((x,)) ((y,=1000)) ((z,)) );
Sposób użycia jest podobny do powyższego (bez funkcji eName ## 2Enum, którą można spróbować ekstrapolować z poprzedniej składni).
Przetestowałem to na Macu i Linuksie, ale pamiętaj, że preprocesor boost :: może nie być w pełni przenośny.
źródło
Łącząc niektóre z technik tutaj, otrzymałem najprostszą formę:
#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; }
źródło
Jeśli używasz gcc, możesz użyć:
const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};
Po prostu zadzwoń na przykład
źródło
Sprawdź pomysły w Mu Dynamics Research Labs - Archiwum bloga . Znalazłem to na początku tego roku - zapomniałem dokładnego kontekstu, w którym go znalazłem - i dostosowałem go do tego kodu. Możemy dyskutować o zaletach dodania litery E z przodu; ma zastosowanie do konkretnego problemu, którego dotyczy problem, ale nie jest częścią ogólnego rozwiązania. Schowałem to w moim folderze z winietami - gdzie trzymam interesujące fragmenty kodu na wypadek, gdyby chciał je później. Wstydzę się powiedzieć, że nie zanotowałem wtedy, skąd ten pomysł.
Nagłówek: paste1.h
/* @(#)File: $RCSfile: paste1.h,v $ @(#)Version: $Revision: 1.1 $ @(#)Last changed: $Date: 2008/05/17 21:38:05 $ @(#)Purpose: Automated Token Pasting */ #ifndef JLSS_ID_PASTE_H #define JLSS_ID_PASTE_H /* * Common case when someone just includes this file. In this case, * they just get the various E* tokens as good old enums. */ #if !defined(ETYPE) #define ETYPE(val, desc) E##val, #define ETYPE_ENUM enum { #endif /* ETYPE */ ETYPE(PERM, "Operation not permitted") ETYPE(NOENT, "No such file or directory") ETYPE(SRCH, "No such process") ETYPE(INTR, "Interrupted system call") ETYPE(IO, "I/O error") ETYPE(NXIO, "No such device or address") ETYPE(2BIG, "Arg list too long") /* * Close up the enum block in the common case of someone including * this file. */ #if defined(ETYPE_ENUM) #undef ETYPE_ENUM #undef ETYPE ETYPE_MAX }; #endif /* ETYPE_ENUM */ #endif /* JLSS_ID_PASTE_H */
Przykładowe źródło:
/* @(#)File: $RCSfile: paste1.c,v $ @(#)Version: $Revision: 1.2 $ @(#)Last changed: $Date: 2008/06/24 01:03:38 $ @(#)Purpose: Automated Token Pasting */ #include "paste1.h" static const char *sys_errlist_internal[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) desc, #include "paste1.h" 0 #undef ETYPE }; static const char *xerror(int err) { if (err >= ETYPE_MAX || err <= 0) return "Unknown error"; return sys_errlist_internal[err]; } static const char*errlist_mnemonics[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) [E ## val] = "E" #val, #include "paste1.h" #undef ETYPE }; #include <stdio.h> int main(void) { int i; for (i = 0; i < ETYPE_MAX; i++) { printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i)); } return(0); }
Niekoniecznie najczystsze na świecie wykorzystanie preprocesora C - ale zapobiega to wielokrotnemu zapisywaniu materiału.
źródło
Tworzenie czegoś zarówno jako identyfikatora C, jak i łańcucha
źródło
Jeśli indeks wyliczenia jest oparty na 0, można umieścić nazwy w tablicy znaków * i zindeksować je wartością wyliczenia.
źródło
#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 nowoprzybyłych
źródło
Stworzyłem prostą klasę matrycy
streamable_enum
że strumień zastosowania operatorów<<
i>>
jest w oparciu ostd::map<Enum, std::string>
:#ifndef STREAMABLE_ENUM_HPP #define STREAMABLE_ENUM_HPP #include <iostream> #include <string> #include <map> template <typename E> class streamable_enum { public: typedef typename std::map<E, std::string> tostr_map_t; typedef typename std::map<std::string, E> fromstr_map_t; streamable_enum() {} streamable_enum(E val) : Val_(val) {} operator E() { return Val_; } bool operator==(const streamable_enum<E>& e) { return this->Val_ == e.Val_; } bool operator==(const E& e) { return this->Val_ == e; } static const tostr_map_t& to_string_map() { static tostr_map_t to_str_(get_enum_strings<E>()); return to_str_; } static const fromstr_map_t& from_string_map() { static fromstr_map_t from_str_(reverse_map(to_string_map())); return from_str_; } private: E Val_; static fromstr_map_t reverse_map(const tostr_map_t& eToS) { fromstr_map_t sToE; for (auto pr : eToS) { sToE.emplace(pr.second, pr.first); } return sToE; } }; template <typename E> streamable_enum<E> stream_enum(E e) { return streamable_enum<E>(e); } template <typename E> typename streamable_enum<E>::tostr_map_t get_enum_strings() { // \todo throw an appropriate exception or display compile error/warning return {}; } template <typename E> std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) { auto& mp = streamable_enum<E>::to_string_map(); auto res = mp.find(e); if (res != mp.end()) { os << res->second; } else { os.setstate(std::ios_base::failbit); } return os; } template <typename E> std::istream& operator>>(std::istream& is, streamable_enum<E>& e) { std::string str; is >> str; if (str.empty()) { is.setstate(std::ios_base::failbit); } auto& mp = streamable_enum<E>::from_string_map(); auto res = mp.find(str); if (res != mp.end()) { e = res->second; } else { is.setstate(std::ios_base::failbit); } return is; } #endif
Stosowanie:
#include "streamable_enum.hpp" using std::cout; using std::cin; using std::endl; enum Animal { CAT, DOG, TIGER, RABBIT }; template <> streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() { return { { CAT, "Cat"}, { DOG, "Dog" }, { TIGER, "Tiger" }, { RABBIT, "Rabbit" } }; } int main(int argc, char* argv []) { cout << "What animal do you want to buy? Our offering:" << endl; for (auto pr : streamable_enum<Animal>::to_string_map()) { // Use from_string_map() and pr.first instead cout << " " << pr.second << endl; // to have them sorted in alphabetical order } streamable_enum<Animal> anim; cin >> anim; if (!cin) { cout << "We don't have such animal here." << endl; } else if (anim == Animal::TIGER) { cout << stream_enum(Animal::TIGER) << " was a joke..." << endl; } else { cout << "Here you are!" << endl; } return 0; }
źródło
Oto rozwiązanie wykorzystujące makra z następującymi funkcjami:
zapisuj każdą wartość wyliczenia tylko raz, więc nie ma podwójnych list do obsługi
nie przechowuj wartości wyliczenia w osobnym pliku, który jest później # uwzględniony, więc mogę zapisać go w dowolnym miejscu
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)
wyszukiwanie powinno być szybkie, więc najlepiej bez przełączników dla tych ogromnych wyliczeń
https://stackoverflow.com/a/20134475/1812866
źródło
Pomyślałem, że rozwiązanie takie jak Boost.Fusion do dostosowywania struktur i klas byłoby fajne, mieli nawet w pewnym momencie możliwość użycia wyliczeń jako sekwencji fusion.
Zrobiłem więc tylko kilka małych makr, aby wygenerować kod do wydrukowania wyliczeń. To nie jest idealne i nie ma nic do zobaczenia z kodem standardowym wygenerowanym przez Boost.Fusion, ale może być używane jak makra Boost Fusion. Naprawdę chcę generować typy potrzebne Boost.Fusion do integracji w tej infrastrukturze, która pozwala drukować nazwy członków struktury, ale stanie się to później, na razie to tylko makra:
#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #include <swissarmyknife/detail/config.hpp> #include <string> #include <ostream> #include <boost/preprocessor/cat.hpp> #include <boost/preprocessor/stringize.hpp> #include <boost/preprocessor/seq/for_each.hpp> #define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C( \ R, unused, ENUMERATION_ENTRY) \ case ENUMERATION_ENTRY: \ return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY); \ break; /** * \brief Adapts ENUM to reflectable types. * * \param ENUM_TYPE To be adapted * \param ENUMERATION_SEQ Sequence of enum states */ #define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ) \ inline std::string to_string(const ENUM_TYPE& enum_value) { \ switch (enum_value) { \ BOOST_PP_SEQ_FOR_EACH( \ SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C, \ unused, ENUMERATION_SEQ) \ default: \ return BOOST_PP_STRINGIZE(ENUM_TYPE); \ } \ } \ \ inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \ os << to_string(value); \ return os; \ } #endif
Stara odpowiedź poniżej jest dość zła, nie używaj jej. :)
Stara odpowiedź:
Szukałem sposobu, który rozwiązuje ten problem bez zbytniej zmiany składni deklaracji wyliczeń. Doszedłem do rozwiązania, które używa preprocesora do pobrania ciągu znaków ze stringified enum.
Jestem w stanie zdefiniować nierzadkie wyliczenia w następujący sposób:
SMART_ENUM(State, enum State { RUNNING, SLEEPING, FAULT, UNKNOWN })
Mogę wchodzić z nimi w interakcje na różne sposoby:
// With a stringstream std::stringstream ss; ss << State::FAULT; std::string myEnumStr = ss.str(); //Directly to stdout std::cout << State::FAULT << std::endl; //to a string std::string myStr = State::to_string(State::FAULT); //from a string State::State myEnumVal = State::from_string(State::FAULT);
Na podstawie następujących definicji:
#define SMART_ENUM(enumTypeArg, ...) \ namespace enumTypeArg { \ __VA_ARGS__; \ std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) { \ os << swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ return os; \ } \ \ std::string to_string(const enumTypeArg& val) { \ return swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ } \ \ enumTypeArg from_string(const std::string &str) { \ return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str); \ } \ } \ namespace swissarmyknife { namespace enums { static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); if (enumVal == count) { std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); return identifiersSubset; } ++count; } while (found != std::string::npos); throw std::runtime_error("The enum declaration provided doesn't contains this state."); } template <typename EnumType> static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); if (identifiersSubset == enumStr) { return static_cast<EnumType>(count); } ++count; } while (found != std::string::npos); throw std::runtime_error("No valid enum value for the provided string"); } }}
Kiedy będę potrzebował wsparcia rzadki enum i kiedy będę miał więcej czasu będę poprawić to_string i from_string implementacje z boost :: Xpressive, ale koszty będą w czasie kompilacji ze względu na ważny szablonów wykonywane i generowany wykonywalne prawdopodobnie będzie naprawdę większy. Ma to jednak tę zaletę, że będzie bardziej czytelny i łatwiejszy w utrzymaniu niż ten brzydki kod ręcznej manipulacji na ciągach.:RE
W przeciwnym razie zawsze używałem boost :: bimap do wykonywania takich mapowań między wartością wyliczeń a ciągiem znaków, ale musi to być obsługiwane ręcznie.
źródło
Ponieważ wolę nie używać makr ze wszystkich zwykłych powodów, użyłem bardziej ograniczonego rozwiązania makr, które ma tę zaletę, że makra deklaracji wyliczenia są wolne. Wady obejmują konieczność kopiowania i wklejania definicji makra dla każdego wyliczenia oraz konieczność jawnego dodawania wywołania makra podczas dodawania wartości do wyliczenia.
std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs) { #define HANDLE(x) case x: os << #x; break; switch (cs) { HANDLE(CaptureState::UNUSED) HANDLE(CaptureState::ACTIVE) HANDLE(CaptureState::CLOSED) } return os; #undef HANDLE }
źródło