Jak przekonwertować zmienną typu wyliczenia na ciąg?

132

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?

psihodelia
źródło
2
Wybierz: tutaj , tutaj , tutaj . To bałagan, wszystkie są częściowo duplikatami.
rubenvb
Kolejny , ten jest bardziej kompletny.
bit2shift
Możliwy duplikat stackoverflow.com/questions/207976/… dla C ++ i stackoverflow.com/questions/9907160/… dla C
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Tyle odpowiedzi, ale nie ma „klasy wyliczeniowej”
Vivick

Odpowiedzi:

71

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.

Bo Persson
źródło
2
Możesz także sprawdzić w czasie kompilacji, czy tablica zawiera oczekiwaną liczbę ciągów.
markh44
2
Wiem, że w tej kwestii jestem w ogromnej mniejszości, ale dla programistów takich jak ja, którzy nie chcą polegać na ogromnych bibliotekach stron trzecich i / lub kodzie pełnym znaków zapytania, aby rozwiązać braki występujące w języku, uważam, że tak jest zdecydowanie najprostsze i najczystsze rozwiązanie dla dzisiejszego standardu. +1
Syndog
17
@Syndog Następnie 56 enumeratorów długo wyliczanych w kodzie produkcyjnym jest aktualizowanych przez tego programistę pod dużą presją, aby zwolnić zaległą funkcję, a on zapomina zaktualizować tablicę indeksowaną wyliczeniowo. Jest to niezauważalne, ponieważ powiązana funkcja drukowania jest używana tylko przez kod debugowania aplikacji. Dwa miesiące później jesteś pierwszym, który faktycznie wykonuje ten kod debugowania: następnie podaje on błędne informacje, więc tracisz pół dnia na budowanie założeń opartych na tych błędnych informacjach, zanim zdasz sobie sprawę, że najpierw musiałeś debugować kod debugowania: projektowanie opiera się na jawnym powielaniu.
Reklama N
2
@AdN Ten projekt jest zły. Mapowanie z wyliczenia na ciąg czytelny dla człowieka nie powinno być implementowane jako tablica ciągów indeksowanych przez wartość wyliczenia. Twoje doświadczenie (prawdopodobnie) pokazuje dlaczego. Mapowanie powinno być jawną tablicą par (wyliczenie, ciąg), więc jeśli zapomnisz dodać wpis dla nowej wartości wyliczenia, otrzymasz „???” jako wyjście, ale przynajmniej nie zepsuje nazw wszystkich innych wyliczeń.
brewbuck
9
@AdN Twój scenariusz jest taki, dlaczego wolę funkcję zawierającą przełącznik (bez klauzuli domyślnej) zamiast tablicy i ustawić przełączniki kompilatora w pliku kompilacji, aby generowały błąd dla przełączenia na wyliczenie, które nie obejmuje wszystkich możliwa wartość. Dodanie nowego wpisu wyliczenia bez aktualizowania odpowiednich instrukcji przełącznika spowoduje błąd kompilacji.
divegeek
133

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 generuje ToStringfunkcję, 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ć ToStringfunkcję jako operator<<przeciążenie, ale myślę, że nieco czystszym jest wymaganie wyraźnego „ ToString” konwersji wartości do postaci ciągu.

Na przykład OS_typewyliczenie 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_typewyglą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.

James McNellis
źródło
7
+1, Mechanizm wdrażania jest przerażający, ale interfejs końcowy jest trudny do pokonania dla elegancji.
deft_code
4
Czy mimo to można to uzyskać, aby umożliwić również podanie wartości liczb całkowitych wyliczenia. Na przykład Windows byłby 3, Linux 5 i Apple 7?
Mark
4
Tak, można zmienić (Windows)na (Windows, 3)następnie zastąpić BOOST_PP_SEQ_ENUMz odpowiednio napisany BOOST_PP_SEQ_FOR_EACH. Nie mam tego przydatnego przykładu, ale mogę napisać, jeśli chcesz.
James McNellis,
2
@JamesMcNellis Zdecydowanie chciałbym otrzymać przykład kodu, który spełnia to, o co prosił Mark, czy byłbyś tak uprzejmy, aby wskazać nam drogę? :)
Omer Raviv
2
UWAGA: preprocesor boost ma sztywny limit 256 elementów. W przypadku większych wyliczeń potrzebne jest inne rozwiązanie.
dshin
34

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 ? :)

Reno
źródło
2
Jest to jedyne rozwiązanie, które działa, gdy wyliczenie ma więcej niż 256 elementów.
dshin
8

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] ;
Nawaz
źródło
7

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"
};
Andrzej
źródło
Założyłem - najwyraźniej błędnie - język programowania jest ograniczony do C.
Andrew
2
jesteś trochę zdyszany, enum jesteś typem w C. Stałe typu wyliczenia całkowego są typu, inta nie enumtypu, 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.
Jens Gustedt
7

Połączyłem rozwiązania Jamesa , Howarda i Édera i stworzyłem bardziej ogólną implementację:

  • Wartość int i niestandardową reprezentację ciągu można opcjonalnie zdefiniować dla każdego elementu wyliczenia
  • Używana jest „klasa wyliczeniowa”

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;
}
Niedźwiedź polarny
źródło
To najlepsza jak dotąd odpowiedź
Arnout
6

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/

M.Ali
źródło
Ten faktycznie powinien być topowy, chociaż wystarczyłby tylko pierwszy :)
pholat
Działa dobrze, ale powinieneś dodać #ifndef stringify na górze, aby uniknąć błędu kompilacji. Zmieniłem również typ wyliczenia na std :: string, zgodnie z sugestią dgmz.
astarakastara
6

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";
}
dgmz
źródło
14
Nie będzie działać, jeśli masz DIRECTION a = NORTH; a następnie napisz ENUM_TO_STR (a)
mathreadler
5

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ę!

Ph0t0n
źródło
Kluczową rzeczą, którą lubię w tym konkretnym podejściu, jest to, że działa z normalną składnią wyliczenia oddzieloną przecinkami (o ile nie zawiera żadnych przypisań ustawień wartości w wyliczeniu). W moim przypadku musiałem pracować z istniejącym wyliczeniem z dużą liczbą członków, więc było to znacznie łatwiejsze do zrobienia niż podejście doładowania.
CuriousKea,
4

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 ++.

Vladimir
źródło
2
Oprócz uczciwego komentarza, że ​​nie ma tu potrzeby Qt, inną kwestią jest to, że można chcieć użyć Boost's bimapna wypadek, gdyby ktoś chciał przeanalizować nazwy i przekształcić je w wyliczenia (np. Z pliku XML).
Dmitri Nesteruk
4
Powinien nie być używając typów Qt w ogólnej C ++ pytanie.
Wektor
4

Dla C99 jest P99_DECLARE_ENUMw P99, który pozwala po prostu zadeklarować w enumten sposób:

P99_DECLARE_ENUM(color, red, green, blue);

a następnie użyj, color_getname(A)aby uzyskać ciąg z nazwą koloru.

Jens Gustedt
źródło
3

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.

gerardw
źródło
2

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)
y2k1234
źródło
2

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
    .................
}
OneOfOne
źródło
1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra
2

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;
  }
Marco Amagliani
źródło
2

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.

Mike Dunlavey
źródło
„Makra X” rzadko są eleganckim rozwiązaniem jakiegokolwiek problemu. W takim przypadku znacznie bardziej czytelne byłoby po prostu zdefiniowanie elementów makra tak, jak #define TEST_1 hello #define TEST_2 worldwtedy, 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.
Lundin
@Lundin: Twierdzę tylko, że 1) działa to nawet z najbardziej prymitywnym kompilatorem C i 2) dodanie lub usunięcie elementu jest edycją 1-wierszową.
Mike Dunlavey
Opublikowałem własną odpowiedź: stackoverflow.com/a/39877228/584518 . Miejmy nadzieję, że uratuje to biedną duszę przed rozwiązaniami makr x.
Lundin
1
Skorzystałem z twojego rozwiązania. Myślę, że to jest najlepsze. Składnia C jest nadal dostępna, dzięki czemu można zrozumieć, co się dzieje, a lista jest definiowana tylko raz. Możesz usunąć zerowy element, umieszczając przecinek po wpisie w DEFINE_ENUM_ELEMENT.
wysłany
1

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 )
Książę
źródło
1
Ta technika nazywa się makrami x!
Watusimoto,
0

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";
}
BЈовић
źródło
0
#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 ) );
Andrey Syrokomskiy
źródło
0

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;
}
Éder
źródło
0

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_SEQzachowa 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.

Howard Gong
źródło
0

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:

  • Unikaj powtarzania kodu (zasada „DRY”).
  • Kod musi być skalowalny, możliwy do utrzymania i bezpieczny, nawet jeśli elementy są dodawane lub usuwane wewnątrz wyliczenia.
  • Cały kod powinien być wysokiej jakości: łatwy do odczytania, łatwy w utrzymaniu.

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ąc test_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))

( intponieważ stałe wyliczenia są w rzeczywistości typu int, a nie test_t)

Zapobiegnie to STRINGIFY(random_stuff)kompilacji kodu .

Lundin
źródło
Rozumiem, co mówisz, ale sprawa pozostaje. Typowe przewidywalne zmiany powinny wymagać minimalnej edycji (np. 1 wiersz). (Myślę, że to jest powód DRY.) Więc tutaj, jeśli rozmiar wyliczenia wynosi 500, a chcesz wstawić nowy element w środku (lub usunąć / zmienić nazwę / zamianę), ile linii kodu musisz zmienić i ile kontroli musisz zrobić, aby upewnić się, że nie popełniłeś błędu? Mogą również istnieć inne fragmenty kodu, które robią coś jednolitego dla każdego elementu listy.
Mike Dunlavey
Dzięki za poinformowanie mnie, że nazywają się one X-macros . Nie wiedziałem tego. To, czego nie widzę, to ogólnie oczernianie ich przez ludzi.
Mike Dunlavey
@MikeDunlavey Bez względu na rozmiar wyliczenia, będziesz musiał zmienić dokładnie 3 linie: dodaj a #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 helloi #define ORANGES worldpo nich nastąpić typedef enum { APPES, ORANGES, TEST_N } test_t;i tak dalej.
Lundin
@MikeDunlavey Jeśli chodzi o makra X, argumenty przeciwko nim są takie same, jak argumenty przeciwko jakimkolwiek makrom podobnym do funkcji. Nie musisz szukać daleko, aby znaleźć wiele bardzo ważnych uwag krytycznych wobec makr podobnych do funkcji.
Lundin
0

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:

  • Nie można przypisać wartości członkom wyliczenia.
  • Wartości członków wyliczenia są używane jako indeks, ale powinno być w porządku, ponieważ wszystko jest zdefiniowane w jednym makrze.
  • Nie można go użyć do zdefiniowania typu wyliczenia wewnątrz klasy.

Jeśli możesz to obejść. Myślę, że zwłaszcza jak go używać, to jest ładne i smukłe. Zalety:

  • Łatwy w użyciu.
  • Nie jest wymagane dzielenie ciągów w czasie wykonywania.
  • Oddzielne ciągi są dostępne w czasie kompilacji.
  • Łatwe do odczytania. Pierwszy zestaw makr może wymagać dodatkowej sekundy, ale nie są one tak skomplikowane.
jokr
źródło
0

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.

Ali Alidoust
źródło
0

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:

MERCURY
EARTH

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ę.

Alias ​​Fakename
źródło
0

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
eungenue
źródło
0
#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)

Michał Turlik
źródło
0

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); }
Joseph Larson
źródło
0

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

TarmoPikaro
źródło
0

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ć.

Marius
źródło