Jak sprawić, by printf pokazywał wartości zmiennych, które są typu wyliczeniowego? Na przykład:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
a ja potrzebuję czegoś takiego
printenum(OS_type, "My OS is %s", myOS);
który musi pokazywać ciąg „Linux”, a nie liczbę całkowitą.
Przypuszczam, że najpierw muszę utworzyć tablicę ciągów indeksowaną wartością. Ale nie wiem, czy to najpiękniejszy sposób na zrobienie tego. Czy to w ogóle możliwe?
c++
c
preprocessor
ansi-c
psihodelia
źródło
źródło
Odpowiedzi:
Naprawdę nie ma na to pięknego sposobu. Po prostu skonfiguruj tablicę ciągów indeksowanych przez wyliczenie.
Jeśli wykonujesz dużo danych wyjściowych, możesz zdefiniować operator <<, który pobiera parametr wyliczeniowy i wyszukuje za Ciebie.
źródło
Naiwnym rozwiązaniem jest oczywiście napisanie funkcji dla każdego wyliczenia wykonującego konwersję na łańcuch:
enum OS_type { Linux, Apple, Windows }; inline const char* ToString(OS_type v) { switch (v) { case Linux: return "Linux"; case Apple: return "Apple"; case Windows: return "Windows"; default: return "[Unknown OS_type]"; } }
Jest to jednak katastrofa konserwacyjna. Z pomocą biblioteki Boost.Preprocessor, która może być używana zarówno z kodem C, jak i C ++, możesz łatwo wykorzystać preprocesor i pozwolić mu wygenerować tę funkcję za Ciebie. Makro generowania wygląda następująco:
#include <boost/preprocessor.hpp> #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case elem : return BOOST_PP_STRINGIZE(elem); #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ }
Pierwsze makro (zaczynające się od
X_
) jest używane wewnętrznie przez drugie. Drugie makro najpierw generuje wyliczenie, a następnie generujeToString
funkcję, która przyjmuje obiekt tego typu i zwraca nazwę modułu wyliczającego jako ciąg (ta implementacja z oczywistych względów wymaga, aby moduły wyliczające były mapowane na unikalne wartości).W C ++ zamiast tego można by zaimplementować
ToString
funkcję jakooperator<<
przeciążenie, ale myślę, że nieco czystszym jest wymaganie wyraźnego „ToString
” konwersji wartości do postaci ciągu.Na przykład
OS_type
wyliczenie byłoby zdefiniowane w następujący sposób:DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))
O ile na pierwszy rzut oka makro wygląda na to, że jest dużo pracy, a definicja
OS_type
wygląda raczej obco, pamiętaj, że musisz napisać makro raz, a potem możesz go używać do każdego wyliczenia. Możesz dodać do niego dodatkowe funkcje (np. Konwersję z postaci łańcuchowej do wyliczenia) bez większych problemów i całkowicie rozwiązuje problem konserwacji, ponieważ wystarczy podać nazwy tylko raz, kiedy wywołujesz makro.Wyliczenie może być następnie użyte tak, jakby zostało zdefiniowane normalnie:
#include <iostream> int main() { OS_type t = Windows; std::cout << ToString(t) << " " << ToString(Apple) << std::endl; }
Fragmenty kodu w tym poście, zaczynające się od
#include <boost/preprocessor.hpp>
wiersza, można skompilować zgodnie z przesłaniem, aby zademonstrować rozwiązanie.To szczególne rozwiązanie jest przeznaczone dla C ++, ponieważ używa specyficznej dla C ++ składni (np. Nie
typedef enum
) i przeciążenia funkcji, ale byłoby proste, aby działało to również z C.źródło
(Windows)
na(Windows, 3)
następnie zastąpićBOOST_PP_SEQ_ENUM
z odpowiednio napisanyBOOST_PP_SEQ_FOR_EACH
. Nie mam tego przydatnego przykładu, ale mogę napisać, jeśli chcesz.To jest blok preprocesora
#ifndef GENERATE_ENUM_STRINGS #define DECL_ENUM_ELEMENT( element ) element #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME #define END_ENUM( ENUM_NAME ) ENUM_NAME; \ char* GetString##ENUM_NAME(enum tag##ENUM_NAME index); #else #define DECL_ENUM_ELEMENT( element ) #element #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] = #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \ tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; } #endif
Definicja wyliczenia
BEGIN_ENUM(Os_type) { DECL_ENUM_ELEMENT(winblows), DECL_ENUM_ELEMENT(hackintosh), } END_ENUM(Os_type)
Zadzwoń za pomocą
GetStringOs_type(winblows);
Zaczerpnięte stąd . Jakie to jest świetne ? :)
źródło
Użyj
std::map<OS_type, std::string>
i wypełnij go wyliczeniem jako kluczem i reprezentacją ciągu jako wartościami, a następnie możesz to zrobić:printf("My OS is %s", enumMap[myOS].c_str()); std::cout << enumMap[myOS] ;
źródło
Problem z wyliczeniami C polega na tym, że nie jest to własny typ, tak jak w C ++. Wyliczenie w C to sposób mapowania identyfikatorów na wartości całkowite. Tylko to. Dlatego wartość wyliczenia jest wymienna z wartościami całkowitymi.
Jak dobrze się domyślasz, dobrym sposobem jest utworzenie mapowania między wartością wyliczenia a łańcuchem. Na przykład:
char * OS_type_label[] = { "Linux", "Apple", "Windows" };
źródło
enum
jesteś typem w C. Stałe typu wyliczenia całkowego są typu,int
a nieenum
typu, przez który są zdefiniowane, być może chciałeś powiedzieć. Ale w ogóle nie rozumiem, co to ma wspólnego z tym pytaniem.Połączyłem rozwiązania Jamesa , Howarda i Édera i stworzyłem bardziej ogólną implementację:
Pełny kod jest napisany poniżej (użyj „DEFINE_ENUM_CLASS_WITH_ToString_METHOD” do zdefiniowania wyliczenia) ( demo online ).
#include <boost/preprocessor.hpp> #include <iostream> // ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from: // http://lists.boost.org/boost-users/2012/09/76055.php // // This macro do the following: // input: // (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr") // output: // ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr")) #define HELPER1(...) ((__VA_ARGS__)) HELPER2 #define HELPER2(...) ((__VA_ARGS__)) HELPER1 #define HELPER1_END #define HELPER2_END #define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END) // CREATE_ENUM_ELEMENT_IMPL works in the following way: // if (elementTuple.GetSize() == 4) { // GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)), // } else { // GENERATE: elementTuple.GetElement(0), // } // Example 1: // CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _)) // generates: // Element1 = 2, // // Example 2: // CREATE_ENUM_ELEMENT_IMPL((Element2, _)) // generates: // Element1, #define CREATE_ENUM_ELEMENT_IMPL(elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) \ ), // we have to add a dummy element at the end of a tuple in order to make // BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element. // if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile. // It requires that a tuple with only one element looked like (Element1,). // Unfortunately I couldn't find a way to make this transformation, so // I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end // of a tuple, in this case the initial tuple will look like (Element1, _) what // makes it compatible with BOOST_PP_TUPLE_ELEM macro #define CREATE_ENUM_ELEMENT(r, data, elementTuple) \ CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _)) #define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element) \ case enumName::element : return BOOST_PP_STRINGIZE(element); #define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation) \ case enumName::element : return stringRepresentation; // GENERATE_CASE_FOR_SWITCH macro generates case for switch operator. // Algorithm of working is the following // if (elementTuple.GetSize() == 1) { // DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0)) // } else { // DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1)) // } // // Example 1: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2)) // generates: // case EnumName::Element1 : return "Element 1 string repr"; // // Example 2: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2)) // generates: // case EnumName::Element2 : return "Element2"; #define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1), \ DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)), \ DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple)) \ ) // DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job #define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements) \ enum class enumName { \ BOOST_PP_SEQ_FOR_EACH( \ CREATE_ENUM_ELEMENT, \ 0, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ }; \ inline const char* ToString(const enumName element) { \ switch (element) { \ BOOST_PP_SEQ_FOR_EACH( \ GENERATE_CASE_FOR_SWITCH, \ enumName, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]"; \ } \ } DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements, (Element1) (Element2, "string representation for Element2 ") (Element3, "Element3 string representation", 1000) (Element4, "Element 4 string repr") (Element5, "Element5", 1005) (Element6, "Element6 ") (Element7) ) // Generates the following: // enum class Elements { // Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6, // }; // inline const char* ToString(const Elements element) { // switch (element) { // case Elements::Element1: return "Element1"; // case Elements::Element2: return "string representation for Element2 "; // case Elements::Element3: return "Element3 string representation"; // case Elements::Element4: return "Element 4 string repr"; // case Elements::Element5: return "Element5"; // case Elements::Element6: return "Element6 "; // case Elements::Element7: return "Element7"; // default: return "[Unknown " "Elements" "]"; // } // } int main() { std::cout << ToString(Elements::Element1) << std::endl; std::cout << ToString(Elements::Element2) << std::endl; std::cout << ToString(Elements::Element3) << std::endl; std::cout << ToString(Elements::Element4) << std::endl; std::cout << ToString(Elements::Element5) << std::endl; std::cout << ToString(Elements::Element6) << std::endl; std::cout << ToString(Elements::Element7) << std::endl; return 0; }
źródło
Czy próbowałeś tego:
#define stringify( name ) # name enum enMyErrorValue { ERROR_INVALIDINPUT = 0, ERROR_NULLINPUT, ERROR_INPUTTOOMUCH, ERROR_IAMBUSY }; const char* enMyErrorValueNames[] = { stringify( ERROR_INVALIDINPUT ), stringify( ERROR_NULLINPUT ), stringify( ERROR_INPUTTOOMUCH ), stringify( ERROR_IAMBUSY ) }; void vPrintError( enMyErrorValue enError ) { cout << enMyErrorValueNames[ enError ] << endl; } int main() { vPrintError((enMyErrorValue)1); }
stringify()
Makro mogą być używane do przekształcić dowolny tekst w kodzie na ciąg znaków, ale tylko dokładny tekst w nawiasach. Nie ma żadnego wyłuskiwania zmiennych, podstawiania makr ani żadnych innych rzeczy.http://www.cplusplus.com/forum/general/2949/
źródło
Ten prosty przykład zadziałał dla mnie. Mam nadzieję że to pomoże.
#include <iostream> #include <string> #define ENUM_TO_STR(ENUM) std::string(#ENUM) enum DIRECTION{NORTH, SOUTH, WEST, EAST}; int main() { std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n"; }
źródło
Jest tu wiele dobrych odpowiedzi, ale pomyślałem, że niektórym osobom moja może się przydać. Podoba mi się to, ponieważ interfejs, którego używasz do definiowania makra, jest tak prosty, jak to tylko możliwe. Jest to również przydatne, ponieważ nie musisz dołączać żadnych dodatkowych bibliotek - wszystko jest dostarczane z C ++ i nie wymaga nawet naprawdę późnej wersji. Wyciągnąłem fragmenty z różnych miejsc w Internecie, więc nie mogę przypisać sobie wszystkiego, ale myślę, że jest to na tyle wyjątkowe, że gwarantuje nową odpowiedź.
Najpierw utwórz plik nagłówkowy ... nazwij go EnumMacros.h lub coś podobnego i umieść w nim to:
// Search and remove whitespace from both ends of the string static std::string TrimEnumString(const std::string &s) { std::string::const_iterator it = s.begin(); while (it != s.end() && isspace(*it)) { it++; } std::string::const_reverse_iterator rit = s.rbegin(); while (rit.base() != it && isspace(*rit)) { rit++; } return std::string(it, rit.base()); } static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax) { std::stringstream ss(szArgs); std::string strSub; int nIdx = 0; while (ss.good() && (nIdx < nMax)) { getline(ss, strSub, ','); Array[nIdx] = TrimEnumString(strSub); nIdx++; } }; // This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT #define DECLARE_ENUM(ename, ...) \ namespace ename { \ enum ename { __VA_ARGS__, COUNT }; \ static std::string _Strings[COUNT]; \ static const char* ToString(ename e) { \ if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \ return _Strings[e].c_str(); \ } \ static ename FromString(const std::string& strEnum) { \ if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \ for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \ return COUNT; \ } \ }
Następnie w swoim głównym programie możesz to zrobić ...
#include "EnumMacros.h" DECLARE_ENUM(OsType, Windows, Linux, Apple) void main() { OsType::OsType MyOs = OSType::Apple; printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT); }
Gdzie wyjście byłoby >> Wartość 'Apple' to: 2 z 4
Cieszyć się!
źródło
Zakładając, że Twoje wyliczenie jest już zdefiniowane, możesz utworzyć tablicę par:
std::pair<QTask::TASK, QString> pairs [] = { std::pair<OS_type, string>(Linux, "Linux"), std::pair<OS_type, string>(Windows, "Windows"), std::pair<OS_type, string>(Apple, "Apple"), };
Teraz możesz utworzyć mapę:
std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));
Teraz możesz korzystać z mapy. Jeśli wyliczenie zostanie zmienione, musisz dodać / usunąć parę z par tablic []. Myślę, że jest to najbardziej elegancki sposób na uzyskanie ciągu znaków z wyliczenia w C ++.
źródło
bimap
na wypadek, gdyby ktoś chciał przeanalizować nazwy i przekształcić je w wyliczenia (np. Z pliku XML).Dla C99 jest
P99_DECLARE_ENUM
w P99, który pozwala po prostu zadeklarować wenum
ten sposób:P99_DECLARE_ENUM(color, red, green, blue);
a następnie użyj,
color_getname(A)
aby uzyskać ciąg z nazwą koloru.źródło
Osobiście wolę zminimalizować zarówno powtarzające się pisanie, jak i trudne do zrozumienia makra, a także unikać wprowadzania definicji makr do ogólnej przestrzeni kompilatora.
Tak więc w pliku nagłówkowym:
enum Level{ /** * zero reserved for internal use */ verbose = 1, trace, debug, info, warn, fatal }; static Level readLevel(const char *);
a implementacja cpp to:
Logger::Level Logger::readLevel(const char *in) { # define MATCH(x) if (strcmp(in,#x) ==0) return x; MATCH(verbose); MATCH(trace); MATCH(debug); MATCH(info); MATCH(warn); MATCH(fatal); # undef MATCH std::string s("No match for logging level "); s += in; throw new std::domain_error(s); }
Zwróć uwagę na #undef makra, gdy tylko z nim skończymy.
źródło
Oto mój kod w C ++:
/* * File: main.cpp * Author: y2k1234 * * Created on June 14, 2013, 9:50 AM */ #include <cstdlib> #include <stdio.h> using namespace std; #define MESSAGE_LIST(OPERATOR) \ OPERATOR(MSG_A), \ OPERATOR(MSG_B), \ OPERATOR(MSG_C) #define GET_LIST_VALUE_OPERATOR(msg) ERROR_##msg##_VALUE #define GET_LIST_SRTING_OPERATOR(msg) "ERROR_"#msg"_NAME" enum ErrorMessagesEnum { MESSAGE_LIST(GET_LIST_VALUE_OPERATOR) }; static const char* ErrorMessagesName[] = { MESSAGE_LIST(GET_LIST_SRTING_OPERATOR) }; int main(int argc, char** argv) { int totalMessages = sizeof(ErrorMessagesName)/4; for (int i = 0; i < totalMessages; i++) { if (i == ERROR_MSG_A_VALUE) { printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_B_VALUE) { printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_C_VALUE) { printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else { printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]); } } return 0; } Output: ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME] ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME] ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME] RUN SUCCESSFUL (total time: 126ms)
źródło
Trochę za późno na imprezę, ale oto moje rozwiązanie w C ++ 11:
namespace std { template<> struct hash<enum_one> { std::size_t operator()(const enum_one & e) const { return static_cast<std::size_t>(e); } }; template<> struct hash<enum_two> { //repeat for each enum type std::size_t operator()(const enum_two & e) const { return static_cast<std::size_t>(e); } }; } const std::string & enum_name(const enum_one & e) { static const std::unordered_map<enum_one, const std::string> names = { #define v_name(n) {enum_one::n, std::string(#n)} v_name(value1), v_name(value2), v_name(value3) #undef v_name }; return names.at(e); } const std::string & enum_name(const enum_two & e) { //repeat for each enum type ................. }
źródło
error: ‘hash’ is not a class template
->#include <functional>
Moje rozwiązanie bez boost:
#ifndef EN2STR_HXX_ #define EN2STR_HXX_ #define MAKE_STRING_1(str ) #str #define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__) #define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__) #define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__) #define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__) #define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__) #define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__) #define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__) #define PRIMITIVE_CAT(a, b) a##b #define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N) (__VA_ARGS__) #define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0 #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N #define PP_NARG_(...) PP_ARG_N(__VA_ARGS__) #define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N()) #define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ }; \ struct NAME##_str { \ static const char * get(const NAME et) { \ static const char* NAME##Str[] = { \ MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) }; \ return NAME##Str[et]; \ } \ }; #endif /* EN2STR_HXX_ */
A oto jak z niego korzystać
int main() { MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d); pippo c = d; cout << pippo_str::get(c) << "\n"; return 0; }
źródło
Kolejny spóźniony na imprezę, używając preprocesora:
1 #define MY_ENUM_LIST \ 2 DEFINE_ENUM_ELEMENT(First) \ 3 DEFINE_ENUM_ELEMENT(Second) \ 4 DEFINE_ENUM_ELEMENT(Third) \ 5 6 //-------------------------------------- 7 #define DEFINE_ENUM_ELEMENT(name) , name 8 enum MyEnum { 9 Zeroth = 0 10 MY_ENUM_LIST 11 }; 12 #undef DEFINE_ENUM_ELEMENT 13 14 #define DEFINE_ENUM_ELEMENT(name) , #name 15 const char* MyEnumToString[] = { 16 "Zeroth" 17 MY_ENUM_LIST 18 }; 19 #undef DEFINE_ENUM_ELEMENT 20 21 #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name; 22 enum MyEnum StringToMyEnum(const char* s){ 23 if (strcmp(s, "Zeroth")==0) return Zeroth; 24 MY_ENUM_LIST 25 return NULL; 26 } 27 #undef DEFINE_ENUM_ELEMENT
(Po prostu wstawiłem numery wierszy, aby łatwiej było o nich mówić). Wiersze 1-4 są tym, co edytujesz, aby zdefiniować elementy wyliczenia. (Nazwałem to „makrem listy”, ponieważ jest to makro, które tworzy listę rzeczy. @Lundin informuje mnie, że jest to dobrze znana technika zwana X-macros).
Linia 7 definiuje wewnętrzne makro, aby wypełnić rzeczywistą deklarację wyliczenia w liniach 8-11. Linia 12. cofa definicję wewnętrznego makra (tylko po to, aby wyciszyć ostrzeżenie kompilatora).
Wiersz 14 definiuje wewnętrzne makro, aby utworzyć wersję łańcuchową nazwy elementu wyliczenia. Następnie wiersze od 15 do 18 generują tablicę, która może konwertować wartość wyliczenia na odpowiedni ciąg.
Wiersze 21-27 generują funkcję, która konwertuje ciąg na wartość wyliczenia lub zwraca NULL, jeśli ciąg nie pasuje do żadnej.
Jest to trochę uciążliwe, ponieważ obsługuje zerowy element. Właściwie pracowałem nad tym w przeszłości.
Przyznaję, że ta technika przeszkadza ludziom, którzy nie chcą myśleć, że sam preprocesor może być zaprogramowany do pisania kodu za Ciebie. Myślę, że dobrze ilustruje różnicę między czytelnością a łatwością konserwacji . Kod jest trudny do odczytania, ale jeśli wyliczenie ma kilkaset elementów, możesz dodawać, usuwać lub zmieniać kolejność elementów i nadal mieć pewność, że wygenerowany kod nie zawiera błędów.
źródło
#define TEST_1 hello #define TEST_2 world
wtedy,typedef enum { TEST_1, TEST_2 } test_t;
a następnie utworzenie tabeli wyszukiwania ciągów, która używa makra stringify:const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), };
Istnieje już wiele odpowiedzi wskazujących na podobne rozwiązania. O wiele bardziej czytelny.Oto metoda Old Skool (używana szeroko w gcc) wykorzystująca tylko preprocesor C. Przydatne, jeśli generujesz dyskretne struktury danych, ale musisz zachować spójną kolejność między nimi. Wpisy w mylist.tbl można oczywiście rozszerzyć na coś znacznie bardziej złożonego.
test.cpp:
enum { #undef XX #define XX(name, ignore) name , #include "mylist.tbl" LAST_ENUM }; char * enum_names [] = { #undef XX #define XX(name, ignore) #name , #include "mylist.tbl" "LAST_ENUM" };
A potem mylist.tbl:
/* A = enum */ /* B = some associated value */ /* A B */ XX( enum_1 , 100) XX( enum_2 , 100 ) XX( enum_3 , 200 ) XX( enum_4 , 900 ) XX( enum_5 , 500 )
źródło
W C ++ w ten sposób:
enum OS_type{Linux, Apple, Windows}; std::string ToString( const OS_type v ) { const std::map< OS_type, std::string > lut = boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows"); std::map< OS_type, std::string >::const_iterator it = lut.find( v ); if ( lut.end() != it ) return it->second; return "NOT FOUND"; }
źródło
#include <EnumString.h>
z http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C i później
enum FORM { F_NONE = 0, F_BOX, F_CUBE, F_SPHERE, };
wstawić
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 wyliczeniu nie są zduplikowane.
Przykładowy kod do konwersji wartości wyliczenia na ciąg:
enum FORM f = ... const std::string& str = EnumString< FORM >::From( f );
Przykładowy kod dla czegoś przeciwnego:
assert( EnumString< FORM >::To( f, str ) );
źródło
Dzięki James za sugestię. Było to bardzo przydatne, więc zaimplementowałem w drugą stronę, aby w jakiś sposób wnieść swój wkład.
#include <iostream> #include <boost/preprocessor.hpp> using namespace std; #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case data::elem : return BOOST_PP_STRINGIZE(elem); #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \ if (BOOST_PP_SEQ_TAIL(data) == \ BOOST_PP_STRINGIZE(elem)) return \ static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum class name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } \ \ inline int ToEnum(std::string s) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF, \ (name)(s), \ enumerators \ ) \ return -1; \ } DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows)); int main(void) { OS_type t = OS_type::Windows; cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl; cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl; return 0; }
źródło
Aby rozszerzyć odpowiedź Jamesa, ktoś potrzebuje przykładowego kodu obsługującego enum define z wartością int, mam również to wymaganie, więc oto mój sposób:
Pierwsza z nich to makro do użytku wewnętrznego, którego używa FOR_EACH:
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem) \ BOOST_PP_IF( \ BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2), \ BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem), \ BOOST_PP_TUPLE_ELEM(0, elem) ),
A oto makro definiujące:
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \ 0, enumerators) };
Więc używając go, możesz napisać w ten sposób:
DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum, ((FIRST, 1)) ((SECOND)) ((MAX, SECOND)) )
który rozwinie się do:
enum MyEnum { FIRST = 1, SECOND, MAX = SECOND, };
Podstawowym pomysłem jest zdefiniowanie SEQ, którego każdy element jest TUPLE, abyśmy mogli dodać wartość do elementu wyliczeniowego. W pętli FOR_EACH sprawdź rozmiar TUPLE pozycji, jeśli rozmiar wynosi 2, rozwiń kod do KEY = VALUE, w przeciwnym razie po prostu zachowaj pierwszy element TUPLE.
Ponieważ wejściowa SEQ to w rzeczywistości TUPLE, więc jeśli chcesz zdefiniować funkcje STRINGIZE, może być konieczne wstępne przetworzenie wejściowych modułów wyliczających, oto makro wykonujące zadanie:
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem) \ BOOST_PP_TUPLE_ELEM(0, elem), #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators) \ BOOST_PP_SEQ_SUBSEQ( \ BOOST_PP_TUPLE_TO_SEQ( \ (BOOST_PP_SEQ_FOR_EACH( \ DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \ )), \ 0, \ BOOST_PP_SEQ_SIZE(enumerators))
Makro
DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ
zachowa tylko pierwszy element w każdej TUPLE, a później przekonwertuje na SEQ, teraz zmodyfikuj kod Jamesa, będziesz miał pełną moc.Moja implementacja może nie jest najprostsza, więc jeśli nie znajdziesz czystego kodu, moja implementacja może być dla Ciebie.
źródło
Czysty, bezpieczny roztwór w czystym standardzie C:
#include <stdio.h> #define STRF(x) #x #define STRINGIFY(x) STRF(x) /* list of enum constants */ #define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, TEST_N } test_t; const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), }; int main() { _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table"); printf("%d %s\n", hello, test_str[hello]); printf("%d %s\n", world, test_str[world]); test_t x = world; printf("%d %s\n", x, test_str[x]); return 0; }
Wynik
0 hello 1 world 1 world
Racjonalne uzasadnienie
Podczas rozwiązywania podstawowego problemu „miej stałe wyliczenia z odpowiednimi ciągami znaków” rozsądny programista wymyśli następujące wymagania:
Pierwszy wymóg, a może także drugi, można spełnić za pomocą różnych niechlujnych rozwiązań makro, takich jak niesławna sztuczka „x makro” lub inne formy magii makr. Problem z takimi rozwiązaniami polega na tym, że zostawiają po sobie kompletnie nieczytelny bałagan tajemniczych makr - nie spełniają trzeciego wymagania powyżej.
Jedyną potrzebną tutaj rzeczą jest posiadanie tabeli wyszukiwania łańcuchów, do której możemy uzyskać dostęp, używając zmiennej wyliczeniowej jako indeksu. Taka tabela musi naturalnie odpowiadać bezpośrednio wyliczeniu i odwrotnie. Kiedy jeden z nich zostanie zaktualizowany, drugi również musi zostać zaktualizowany, inaczej nie będzie działać.
Wyjaśnienie kodu
Załóżmy, że mamy wyliczenie podobne
typedef enum { hello, world } test_t;
Można to zmienić na
#define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, } test_t;
Mając tę zaletę, że te stałe makra mogą być teraz używane w innym miejscu, na przykład do generowania tabeli przeglądowej ciągów. Konwersję stałej preprocesora na łańcuch można przeprowadzić za pomocą makra „stringify”:
#define STRF(x) #x #define STRINGIFY(x) STRF(x) const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), };
I to wszystko. Używając
hello
, otrzymujemy stałą wyliczenia o wartości 0. Używająctest_str[hello]
otrzymujemy ciąg „hello”.Aby tabela wyliczenia i tabela przeglądowa były ze sobą bezpośrednio zgodne, musimy upewnić się, że zawierają one tę samą liczbę elementów. Jeśli ktoś będzie utrzymywał kod i zmienił tylko wyliczenie, a nie tabelę przeglądową, lub odwrotnie, ta metoda nie zadziała.
Rozwiązaniem jest wyliczenie, które powie Ci, ile elementów zawiera. Jest do tego powszechnie używana sztuczka w C, po prostu dodaj element na końcu, który wypełnia tylko cel wskazania, ile elementów ma wyliczenie:
typedef enum { TEST_0, TEST_1, TEST_N // will have value 2, there are 2 enum constants in this enum } test_t;
Teraz możemy sprawdzić w czasie kompilacji, czy liczba elementów w wyliczeniu jest równa liczbie elementów w tabeli przeglądowej, najlepiej z potwierdzeniem statycznym C11:
_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table");
(Istnieją brzydkie, ale w pełni funkcjonalne sposoby tworzenia statycznych potwierdzeń w starszych wersjach standardu C, jeśli ktoś nalega na używanie kompilatorów dinozaurów. Jeśli chodzi o C ++, obsługuje on również potwierdzenia statyczne).
Na marginesie, w C11 możemy również osiągnąć większe bezpieczeństwo typów, zmieniając makro stringify:
#define STRINGIFY(x) _Generic((x), int : STRF(x))
(
int
ponieważ stałe wyliczenia są w rzeczywistości typuint
, a nietest_t
)Zapobiegnie to
STRINGIFY(random_stuff)
kompilacji kodu .źródło
#define
, dodaj odniesienie do tego zdefiniowanego w deklaracji wyliczenia i tabeli przeglądowej. Jeśli dodasz te linie, program się nie skompiluje. Numery, które dodałem do identyfikatorów, nie są w żadnym wypadku obowiązkowe, równie dobrze można napisać#define APPLES hello
i#define ORANGES world
po nich nastąpićtypedef enum { APPES, ORANGES, TEST_N } test_t;
i tak dalej.To, co zrobiłem, jest połączeniem tego, co widziałem tutaj i podobnych pytań na tej stronie. Zrobiłem to jest Visual Studio 2013. Nie testowałem tego z innymi kompilatorami.
Przede wszystkim definiuję zestaw makr, które wykonają sztuczki.
// concatenation macros #define CONCAT_(A, B) A ## B #define CONCAT(A, B) CONCAT_(A, B) // generic expansion and stringification macros #define EXPAND(X) X #define STRINGIFY(ARG) #ARG #define EXPANDSTRING(ARG) STRINGIFY(ARG) // number of arguments macros #define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N #define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)) // argument extraction macros #define FIRST_ARG(ARG, ...) ARG #define REST_ARGS(ARG, ...) __VA_ARGS__ // arguments to strings macros #define ARGS_STR__(N, ...) ARGS_STR_##N(__VA_ARGS__) #define ARGS_STR_(N, ...) ARGS_STR__(N, __VA_ARGS__) #define ARGS_STR(...) ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__) #define ARGS_STR_1(ARG) EXPANDSTRING(ARG) #define ARGS_STR_2(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_3(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_4(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_5(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_6(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_7(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_8(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_9(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_10(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_11(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_12(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_13(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_14(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_15(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_16(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_17(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_18(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_19(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_20(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__))) // expand until _100 or as much as you need
Następnie zdefiniuj jedno makro, które utworzy klasę wyliczenia i funkcje pobierające ciągi.
#define ENUM(NAME, ...) \ enum class NAME \ { \ __VA_ARGS__ \ }; \ \ static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) }; \ \ inline const std::string& ToString(NAME value) \ { \ return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)]; \ } \ \ inline std::ostream& operator<<(std::ostream& os, NAME value) \ { \ os << ToString(value); \ return os; \ }
Teraz definiowanie typu wyliczenia i posiadanie dla niego ciągów staje się naprawdę łatwe. Wystarczy, że:
ENUM(MyEnumType, A, B, C);
Aby go przetestować, można użyć następujących wierszy.
int main() { std::cout << MyEnumTypeStrings.size() << std::endl; std::cout << ToString(MyEnumType::A) << std::endl; std::cout << ToString(MyEnumType::B) << std::endl; std::cout << ToString(MyEnumType::C) << std::endl; std::cout << MyEnumType::A << std::endl; std::cout << MyEnumType::B << std::endl; std::cout << MyEnumType::C << std::endl; auto myVar = MyEnumType::A; std::cout << myVar << std::endl; myVar = MyEnumType::B; std::cout << myVar << std::endl; myVar = MyEnumType::C; std::cout << myVar << std::endl; return 0; }
Spowoduje to wyświetlenie:
3 A B C A B C A B C
Uważam, że jest bardzo czysty i łatwy w użyciu. Istnieją pewne ograniczenia:
Jeśli możesz to obejść. Myślę, że zwłaszcza jak go używać, to jest ładne i smukłe. Zalety:
źródło
Czystym rozwiązaniem tego problemu byłoby:
#define RETURN_STR(val, e) {if (val == e) {return #e;}} std::string conv_dxgi_format_to_string(int value) { RETURN_STR(value, DXGI_FORMAT_UNKNOWN); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT); /* ... */ return "<UNKNOWN>"; }
Zaletą tego rozwiązania jest to, że jest proste, a konstruowanie funkcji można łatwo wykonać poprzez kopiowanie i zastępowanie. Zwróć uwagę, że jeśli masz zamiar wykonać wiele konwersji, a wyliczenie ma zbyt wiele możliwych wartości, to rozwiązanie może obciążyć procesor.
źródło
Jestem trochę spóźniony, ale oto moje rozwiązanie wykorzystujące g ++ i tylko standardowe biblioteki. Próbowałem zminimalizować zanieczyszczenie przestrzeni nazw i wyeliminować potrzebę ponownego wpisywania nazw wyliczeń.
Plik nagłówkowy „my_enum.hpp” to:
#include <cstring> namespace ENUM_HELPERS{ int replace_commas_and_spaces_with_null(char* string){ int i, N; N = strlen(string); for(i=0; i<N; ++i){ if( isspace(string[i]) || string[i] == ','){ string[i]='\0'; } } return(N); } int count_words_null_delim(char* string, int tot_N){ int i; int j=0; char last = '\0'; for(i=0;i<tot_N;++i){ if((last == '\0') && (string[i]!='\0')){ ++j; } last = string[i]; } return(j); } int get_null_word_offsets(char* string, int tot_N, int current_w){ int i; int j=0; char last = '\0'; for(i=0; i<tot_N; ++i){ if((last=='\0') && (string[i]!='\0')){ if(j == current_w){ return(i); } ++j; } last = string[i]; } return(tot_N); //null value for offset } int find_offsets(int* offsets, char* string, int tot_N, int N_words){ int i; for(i=0; i<N_words; ++i){ offsets[i] = get_null_word_offsets(string, tot_N, i); } return(0); } } #define MAKE_ENUM(NAME, ...) \ namespace NAME{ \ enum ENUM {__VA_ARGS__}; \ char name_holder[] = #__VA_ARGS__; \ int name_holder_N = \ ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \ int N = \ ENUM_HELPERS::count_words_null_delim( \ name_holder, name_holder_N); \ int offsets[] = {__VA_ARGS__}; \ int ZERO = \ ENUM_HELPERS::find_offsets( \ offsets, name_holder, name_holder_N, N); \ char* tostring(int i){ \ return(&name_holder[offsets[i]]); \ } \ }
Przykład użycia:
#include <cstdio> #include "my_enum.hpp" MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS) int main(int argc, char** argv){ Planets::ENUM a_planet = Planets::EARTH; printf("%s\n", Planets::tostring(Planets::MERCURY)); printf("%s\n", Planets::tostring(a_planet)); }
Spowoduje to wyświetlenie:
Wystarczy zdefiniować wszystko raz, przestrzeń nazw nie powinna być zanieczyszczona, a wszystkie obliczenia są wykonywane tylko raz (reszta to tylko wyszukiwania). Jednak nie uzyskujesz bezpieczeństwa typów klas wyliczeniowych (nadal są to tylko krótkie liczby całkowite), nie możesz przypisać wartości do wyliczeń, musisz zdefiniować wyliczenia w miejscu, w którym można zdefiniować przestrzenie nazw (np. Globalnie).
Nie jestem pewien, jak dobra jest na tym wydajność, lub czy to dobry pomysł (nauczyłem się C przed C ++, więc mój mózg nadal działa w ten sposób). Jeśli ktoś wie, dlaczego jest to zły pomysł, nie krępuj się.
źródło
Jest rok 2017, ale pytanie wciąż żyje
Jeszcze inny sposób:
#include <iostream> #define ERROR_VALUES \ ERROR_VALUE(NO_ERROR, 0, "OK") \ ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \ ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage") enum Error { #define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE, ERROR_VALUES #undef ERROR_VALUE }; inline std::ostream& operator<<(std::ostream& os, Error err) { int errVal = static_cast<int>(err); switch (err) { #define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT; ERROR_VALUES #undef ERROR_VALUE default: // If the error value isn't found (shouldn't happen) return os << errVal; } } int main() { std::cout << "Error: " << NO_ERROR << std::endl; std::cout << "Error: " << FILE_NOT_FOUND << std::endl; std::cout << "Error: " << LABEL_UNINITIALISED << std::endl; return 0; }
Wyjścia:
Error: [0]NO_ERROR, OK Error: [1]FILE_NOT_FOUND, Not found Error: [2]LABEL_UNINITIALISED, Uninitialized usage
źródło
#pragma once #include <string> #include <vector> #include <sstream> #include <algorithm> namespace StringifyEnum { static std::string TrimEnumString(const std::string &s) { std::string::const_iterator it = s.begin(); while (it != s.end() && isspace(*it)) { it++; } std::string::const_reverse_iterator rit = s.rbegin(); while (rit.base() != it && isspace(*rit)) { ++rit; } return std::string(it, rit.base()); } static std::vector<std::string> SplitEnumArgs(const char* szArgs, int nMax) { std::vector<std::string> enums; std::stringstream ss(szArgs); std::string strSub; int nIdx = 0; while (ss.good() && (nIdx < nMax)) { getline(ss, strSub, ','); enums.push_back(StringifyEnum::TrimEnumString(strSub)); ++nIdx; } return std::move(enums); } } #define DECLARE_ENUM_SEQ(ename, n, ...) \ enum class ename { __VA_ARGS__ }; \ const int MAX_NUMBER_OF_##ename(n); \ static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \ inline static std::string ename##ToString(ename e) { \ return ename##Strings.at((int)e); \ } \ inline static ename StringTo##ename(const std::string& en) { \ const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \ if (it != ename##Strings.end()) \ return (ename) std::distance(ename##Strings.begin(), it); \ throw std::runtime_error("Could not resolve string enum value"); \ }
To jest rozbudowana wersja wyliczenia klasy rozszerzonej ... nie dodaje żadnej innej wartości wyliczenia poza podanymi.
Użycie: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)
źródło
Potrzebowałem tego do pracy w obu kierunkach ORAZ często osadzam moje wyliczenia wewnątrz klasy zawierającej, więc zacząłem od rozwiązania Jamesa McNellisa, na samym początku tych odpowiedzi, ale zrobiłem to rozwiązanie. Zwróć też uwagę, że wolę klasę wyliczenia zamiast zwykłego wyliczenia, co nieco komplikuje odpowiedź.
#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem); // The data portion of the FOR_EACH should be (variable type)(value) #define X_DEFINE_ENUMERATION2(r, dataseq, elem) \ if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem; #define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators) \ enum class name { \ Undefined, \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ modifier const char* ToString(const name & v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUMERATION, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } \ \ modifier const name toFunctionName(const std::string & value) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUMERATION2, \ (name)(value), \ enumerators \ ) \ return name::Undefined; \ } #define DEFINE_ENUMERATION(name, toFunctionName, enumerators) \ DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators) #define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators) \ DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)
Aby użyć go w klasie, możesz zrobić coś takiego:
class ComponentStatus { public: /** This is a simple bad, iffy, and good status. See other places for greater details. */ DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN) }
Napisałem test CppUnit, który pokazuje, jak go używać:
void ComponentStatusTest::testSimple() { ComponentStatus::Status value = ComponentStatus::Status::RED; const char * valueStr = ComponentStatus::ToString(value); ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr)); CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr); CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value); } DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN)) void ComponentStatusTest::testOutside() { Status value = Status::RED; const char * valueStr = ToString(value); Status convertedValue = toStatus(string(valueStr)); CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr); CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value); }
Musisz wybrać makro, którego chcesz użyć, DEFINE_ENUMERATION lub DEFINE_ENUMERATION_INSIDE_CLASS. Zobaczysz, że użyłem tego drugiego podczas definiowania ComponentStatus :: Status, ale użyłem tego pierwszego, gdy definiowałem Status. Różnica jest prosta. Wewnątrz klasy prefiksuję metody do / z jako „statyczne”, a jeśli nie są w klasie, używam „inline”. Drobne różnice, ale konieczne.
Niestety, nie sądzę, aby można było tego łatwo uniknąć:
const char * valueStr = ComponentStatus::ToString(value);
chociaż możesz ręcznie utworzyć wbudowaną metodę po definicji klasy, która po prostu łączy się z metodą klasy, coś takiego:
inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
źródło
Moja własna odpowiedź, nie używam doładowania - używając własnego podejścia bez ciężkiej magii definiowania, a to rozwiązanie ma ograniczenie polegające na niemożności zdefiniowania określonej wartości wyliczenia.
#pragma once #include <string> template <class Enum> class EnumReflect { public: static const char* getEnums() { return ""; } }; #define DECLARE_ENUM(name, ...) \ enum name { __VA_ARGS__ }; \ template <> \ class EnumReflect<##name> { \ public: \ static const char* getEnums() { return #__VA_ARGS__; } \ }; /* Basic usage: Declare enumeration: DECLARE_ENUM( enumName, enumValue1, enumValue2, enumValue3, // comment enumValue4 ); Conversion logic: From enumeration to string: printf( EnumToString(enumValue3).c_str() ); From string to enumeration: enumName value; if( !StringToEnum("enumValue4", value) ) printf("Conversion failed..."); WARNING: At the moment assigning enum value to specific number is not supported. */ // // Converts enumeration to string, if not found - empty string is returned. // template <class T> std::string EnumToString(T t) { const char* enums = EnumReflect<T>::getEnums(); const char *token, *next = enums - 1; int id = (int)t; do { token = next + 1; if (*token == ' ') token++; next = strchr(token, ','); if (!next) next = token + strlen(token); if (id == 0) return std::string(token, next); id--; } while (*next != 0); return std::string(); } // // Converts string to enumeration, if not found - false is returned. // template <class T> bool StringToEnum(const char* enumName, T& t) { const char* enums = EnumReflect<T>::getEnums(); const char *token, *next = enums - 1; int id = 0; do { token = next + 1; if (*token == ' ') token++; next = strchr(token, ','); if (!next) next = token + strlen(token); if (strncmp(token, enumName, next - token) == 0) { t = (T)id; return true; } id++; } while (*next != 0); return false; }
Najnowszą wersję można znaleźć na github tutaj:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h
źródło
Istnieje wiele innych odpowiedzi na to pytanie, ale myślę, że lepszym sposobem jest użycie funkcji C ++ 17 i użycie constexpr, aby tłumaczenia były wykonywane w czasie kompilacji. Jest to bezpieczne dla typu i nie musimy bawić się makrami. Zobacz poniżej:
//enum.hpp #include <array> #include <string_view> namespace Enum { template <class ENUM_TYPE, size_t SIZE> constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1) { index = (index == -1) ? map.size() : index; return (index == 0) ? throw "Value not in map": (std::string_view(map[index - 1].second) == value) ? map[index- 1].first: findKey(value, map, index - 1); }; template <class ENUM_TYPE, size_t SIZE> constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1) { index = (index == -1) ? map.size() : index; return (index == 0) ? throw "Key not in map": (map[index - 1].first == key) ? map[index- 1].second: findValue(key, map, index - 1); }; } //test_enum.hpp #include "enum.hpp" namespace TestEnum { enum class Fields { Test1, Test2, Test3, //This has to be at the end NUMBER_OF_FIELDS }; constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap() { std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map = { { {Fields::Test1, "Test1"}, {Fields::Test2, "Test2"}, {Fields::Test3, "Test3"}, } }; return map; }; constexpr Fields StringToEnum(const char * value) { return Enum::findKey(value, GetMap()); } constexpr const char * EnumToString(Fields key) { return Enum::findValue(key, GetMap()); } }
Można to łatwo wykorzystać, aby błędy klucza ciągu były wykrywane w czasie kompilacji:
#include "test_enum.hpp" int main() { auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2 auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1" auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure return 0; }
Kod jest bardziej szczegółowy niż niektóre inne rozwiązania, ale możemy łatwo wykonać konwersję Enum na String i String na Enum w czasie kompilacji i wykryć błędy typu. W przypadku niektórych przyszłych funkcji C ++ 20 można to prawdopodobnie nieco bardziej uprościć.
źródło