Wie konvertiere ich eine Variable vom Typ Enum in einen String?

128

Wie mache ich printf, um die Werte von Variablen anzuzeigen, die vom Typ enum sind? Zum Beispiel:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

und was ich brauche ist so etwas

printenum(OS_type, "My OS is %s", myOS);

die eine Zeichenfolge "Linux" zeigen muss, keine Ganzzahl.

Ich nehme an, zuerst muss ich ein wertindiziertes Array von Zeichenfolgen erstellen. Aber ich weiß nicht, ob das der schönste Weg ist, es zu tun. Ist das überhaupt möglich?

Psihodelia
quelle
2
Treffen Sie Ihre Wahl: hier , hier , hier . Es ist ein Durcheinander, sie sind alle Halbduplikate.
Rubenvb
Ein anderer , dieser ist vollständiger.
Bit2shift
Mögliches Duplikat von stackoverflow.com/questions/207976/… für C ++ und stackoverflow.com/questions/9907160/… für C
Ciro Santilli 法轮功 冠状 病 六四 事件 11
So viele Antworten noch keine "Aufzählungsklasse"
Vivick

Antworten:

70

Es gibt wirklich keinen schönen Weg, dies zu tun. Richten Sie einfach ein Array von Zeichenfolgen ein, die durch die Aufzählung indiziert werden.

Wenn Sie viel ausgeben, können Sie einen Operator << definieren, der einen Enum-Parameter verwendet und die Suche für Sie durchführt.

Bo Persson
quelle
2
Außerdem können Sie beim Kompilieren überprüfen, ob Ihr Array die erwartete Anzahl von Zeichenfolgen enthält.
Markh44
2
Ich weiß, dass ich damit in der großen Minderheit bin, aber für Programmierer wie mich, die sich nicht auf umfangreiche Bibliotheken von Drittanbietern und / oder von Marcos durchsetzten Code verlassen möchten, um sprachliche Mängel zu beheben, ist dies meiner Meinung nach der Fall Die mit Abstand einfachste und reinste Lösung für den heutigen Standard. +1
Syndog
13
@Syndog Dann wird die 56 Enumeratoren lange Aufzählung in Ihrem Produktionscode von diesem Programmierer unter großem Druck aktualisiert, eine überfällige Funktion freizugeben, und er vergisst, dieses enumindizierte Array zu aktualisieren. Dies bleibt unbemerkt, da die zugehörige Druckfunktion nur vom Anwendungs-Debugging-Code verwendet wird. 2 Monate später sind Sie der erste, der diesen Debug-Code tatsächlich ausführt: Er gibt Ihnen dann die falschen Informationen, sodass Sie einen halben Tag verlieren, wenn Sie Annahmen auf der Grundlage dieser falschen Informationen erstellen, bevor Sie feststellen, dass Sie den Debug-Code zuerst debuggen mussten: the Design basiert auf expliziter Vervielfältigung.
Ad N
1
@AdN Das Design ist falsch. Die Zuordnung von Aufzählung zu lesbarer Zeichenfolge sollte nicht als Array von Zeichenfolgen implementiert werden, die durch den Aufzählungswert indiziert werden. Ihre Erfahrung zeigt (vermutlich) warum. Die Zuordnung sollte ein explizites Array von (Aufzählungs-, Zeichenfolgen-) Paaren sein. Wenn Sie also vergessen, einen Eintrag für Ihren neuen Aufzählungswert hinzuzufügen, erhalten Sie "???" als Ausgabe, aber zumindest werden die Namen aller anderen Aufzählungen nicht durcheinander gebracht.
Brewbuck
8
@AdN Ihr Szenario ist, warum ich eine Funktion bevorzuge, die einen Schalter (ohne Standardklausel) anstelle eines Arrays enthält, und die Compiler-Schalter in der Build-Datei so einzustellen, dass ein Fehler für einen Wechsel über eine Aufzählung ausgegeben wird, die nicht alle abdeckt mögliche Werte. Das Hinzufügen eines neuen Enum-Eintrags ohne Aktualisierung der entsprechenden switch-Anweisungen führt zu einem Kompilierungsfehler.
Divegeek
131

Die naive Lösung besteht natürlich darin, für jede Aufzählung, die die Konvertierung in einen String durchführt, eine Funktion zu schreiben:

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

Dies ist jedoch eine Wartungskatastrophe. Mit Hilfe der Boost.Preprocessor-Bibliothek, die sowohl mit C- als auch mit C ++ - Code verwendet werden kann, können Sie den Präprozessor problemlos nutzen und diese Funktion für Sie generieren lassen. Das Generierungsmakro lautet wie folgt:

#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) "]";         \
        }                                                                     \
    }

Das erste Makro (beginnend mit X_) wird intern vom zweiten verwendet. Das zweite Makro generiert zuerst die Aufzählung, generiert dann eine ToStringFunktion, die ein Objekt dieses Typs verwendet und den Namen des Aufzählers als Zeichenfolge zurückgibt (diese Implementierung erfordert aus offensichtlichen Gründen, dass die Aufzähler eindeutigen Werten zugeordnet werden).

In C ++ könnten Sie die ToStringFunktion operator<<stattdessen als Überladung implementieren , aber ich denke, es ist etwas sauberer, ein explizites " ToString" zu benötigen , um den Wert in eine Zeichenfolgenform zu konvertieren.

Als Verwendungsbeispiel OS_typewürde Ihre Aufzählung wie folgt definiert:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

Während das Makro zunächst so aussieht, als wäre es eine Menge Arbeit, und die Definition von OS_typesieht eher fremd aus, denken Sie daran, dass Sie das Makro einmal schreiben müssen, dann können Sie es für jede Aufzählung verwenden. Sie können ihm ohne allzu große Probleme zusätzliche Funktionen hinzufügen (z. B. eine Konvertierung von Zeichenfolgen in Aufzählungszeichen), und das Wartungsproblem wird vollständig gelöst, da Sie die Namen beim Aufrufen des Makros nur einmal angeben müssen.

Die Aufzählung kann dann so verwendet werden, als ob sie normal definiert wäre:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

Die Code-Schnipsel in diesem Beitrag, beginnend mit der #include <boost/preprocessor.hpp>Zeile, können wie veröffentlicht kompiliert werden, um die Lösung zu demonstrieren.

Diese spezielle Lösung ist für C ++ gedacht, da sie C ++ - spezifische Syntax (z. B. no typedef enum) und Funktionsüberladung verwendet. Es wäre jedoch unkompliziert, dies auch mit C zu tun.

James McNellis
quelle
7
+1, Die Implementierungsmaschinerie ist beängstigend, aber die Endschnittstelle ist für Eleganz kaum zu übertreffen.
Deft_code
4
Gibt es sowieso, um dies zu bekommen, damit Sie auch die Enum-Integer-Werte angeben können. Zum Beispiel wäre Windows 3, Linux 5 und Apple 7?
Mark
4
Ja, Sie können das ändern (Windows)und (Windows, 3)dann durch BOOST_PP_SEQ_ENUMein entsprechend geschriebenes ersetzen BOOST_PP_SEQ_FOR_EACH. Ich habe kein Beispiel dafür, aber ich kann eines schreiben, wenn Sie möchten.
James McNellis
2
@JamesMcNellis Ich hätte definitiv gerne ein Beispiel für einen Code, der das erfüllt, wonach Mark gefragt hat. Würden Sie uns so freundlich den Weg zeigen? :)
Omer Raviv
2
HINWEIS: Der Boost-Präprozessor hat eine feste Grenze von 256 Elementen. Für größere Aufzählungen wird eine andere Lösung benötigt.
Dshin
32

Dies ist der Vorprozessorblock

#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

Aufzählung Definition

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Rufen Sie mit an

GetStringOs_type(winblows);

Von hier genommen . Wie cool ist das ? :) :)

Reno
quelle
1
Dies ist die einzige Lösung, die funktioniert, wenn Ihre Aufzählung mehr als 256 Elemente enthält.
Dshin
8

Verwenden std::map<OS_type, std::string>und füllen Sie es mit enum als Schlüssel und Zeichenfolgendarstellung als Werte. Dann können Sie Folgendes tun:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;
Nawaz
quelle
7

Das Problem mit C-Enums ist, dass es kein eigener Typ ist, wie es in C ++ ist. Eine Aufzählung in C ist eine Möglichkeit, Bezeichner Integralwerten zuzuordnen. Nur das. Aus diesem Grund ist ein Aufzählungswert mit ganzzahligen Werten austauschbar.

Wie Sie richtig erraten haben, besteht eine gute Möglichkeit darin, eine Zuordnung zwischen dem Aufzählungswert und einer Zeichenfolge zu erstellen. Beispielsweise:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
Andrew
quelle
Ich nahm an - anscheinend falsch -, dass die Programmiersprache auf C beschränkt ist.
Andrew
1
Sie sind ein bisschen daneben, enum sind Typen in C. Integrale Aufzählungstypkonstanten sind vom Typ intund nicht vom enumTyp, durch den sie definiert sind, ist vielleicht das, was Sie sagen wollten. Aber ich sehe überhaupt nicht, was das mit der Frage zu tun hat.
Jens Gustedt
7

Ich habe die Lösungen von James , Howard und Éder kombiniert und eine allgemeinere Implementierung erstellt:

  • Int-Wert und benutzerdefinierte Zeichenfolgendarstellung können optional für jedes Enum-Element definiert werden
  • "enum class" wird verwendet

Der vollständige Code wird unten geschrieben (verwenden Sie "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" zum Definieren einer Aufzählung) ( Online-Demo ).

#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;
}
Eisbär
quelle
Dies ist die bisher beste Antwort
Arnout
6

Dieses einfache Beispiel hat bei mir funktioniert. Hoffe das hilft.

#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
quelle
13
Funktioniert nicht, wenn Sie RICHTUNG a = NORD haben; und dann schreiben Sie ENUM_TO_STR (a)
Mathreadler
5

Hast du das versucht:

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

Das stringify()Makro kann verwendet werden, um jeden Text in Ihrem Code in eine Zeichenfolge umzuwandeln, aber nur den genauen Text zwischen den Klammern. Es gibt keine variablen Dereferenzen oder Makrosubstitutionen oder andere Dinge, die getan werden.

http://www.cplusplus.com/forum/general/2949/

M.Ali
quelle
Dieser soll eigentlich top sein, obwohl nur der erste ausreichen würde :)
pholat
Funktioniert einwandfrei, aber Sie sollten oben #ifndef stringify hinzufügen, um Kompilierungsfehler zu vermeiden. Ich habe auch den Aufzählungstyp als std :: string geändert, wie dgmz es vorgeschlagen hat.
Astarakastara
5

Hier gibt es viele gute Antworten, aber ich dachte, einige Leute finden meine vielleicht nützlich. Ich mag es, weil die Schnittstelle, die Sie zum Definieren des Makros verwenden, so einfach wie möglich ist. Es ist auch praktisch, weil Sie keine zusätzlichen Bibliotheken hinzufügen müssen - alles wird mit C ++ geliefert und erfordert nicht einmal eine wirklich späte Version. Ich habe Stücke von verschiedenen Orten online gezogen, damit ich nicht alles gutschreiben kann, aber ich denke, es ist einzigartig genug, um eine neue Antwort zu rechtfertigen.

Erstellen Sie zuerst eine Header-Datei ... nennen Sie sie EnumMacros.h oder so ähnlich und fügen Sie diese ein:

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

Dann können Sie dies in Ihrem Hauptprogramm tun ...

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

Wo die Ausgabe wäre >> Der Wert von 'Apple' ist: 2 von 4

Genießen!

Ph0t0n
quelle
Das Wichtigste, was ich an diesem speziellen Ansatz mag, ist, dass er mit der normalen durch Kommas getrennten Syntax der Aufzählung funktioniert (solange er keine Zuweisungen für die Werteinstellung in der Aufzählung enthält). In meinem Fall musste ich mit einer vorhandenen Enumeration mit einer großen Anzahl von Mitgliedern arbeiten, daher war dies viel einfacher als der Boost-Ansatz.
CuriousKea
4

Angenommen, Ihre Aufzählung ist bereits definiert, können Sie ein Array von Paaren erstellen:

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"),
};

Jetzt können Sie eine Karte erstellen:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Jetzt können Sie die Karte verwenden. Wenn Ihre Aufzählung geändert wird, müssen Sie Paare zu Array-Paaren hinzufügen / entfernen []. Ich denke, dass es der eleganteste Weg ist, einen String aus enum in C ++ zu erhalten.

Vladimir
quelle
2
Abgesehen von dem fairen Kommentar, dass Qt hier nicht benötigt wird, ist ein weiterer Punkt, dass man bimapBoosts verwenden möchte, falls man Namen analysieren und in Aufzählungen umwandeln möchte (z. B. aus einer XML-Datei).
Dmitri Nesteruk
4
Sollte keine Qt-Typen in einer generischen C ++ - Frage verwenden.
Vektor
3

Für C99 gibt es P99_DECLARE_ENUMin P99 , mit dem Sie einfach Folgendes deklarieren enumkönnen:

P99_DECLARE_ENUM(color, red, green, blue);

und verwenden Sie dann color_getname(A), um eine Zeichenfolge mit dem Farbnamen zu erhalten.

Jens Gustedt
quelle
2

Hier ist mein C ++ - Code:

/* 
 * 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
quelle
2

Ein bisschen spät zur Party, aber hier ist meine C ++ 11-Lösung:

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
quelle
1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra
2

Ich bevorzuge es, sowohl wiederholte Eingaben als auch schwer verständliche Makros zu minimieren und zu vermeiden, Makrodefinitionen in den allgemeinen Compilerbereich einzuführen.

Also, in der Header-Datei:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

und die cpp-Implementierung ist:

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

Beachten Sie das #undef des Makros, sobald wir damit fertig sind.

gerardw
quelle
2

Meine Lösung ohne 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_ */

Und hier ist, wie man es benutzt

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }
Marco Amagliani
quelle
2

Ein weiterer Verspäteter auf der Party mit dem Präprozessor:

 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

(Ich habe nur Zeilennummern eingegeben, damit Sie leichter darüber sprechen können.) Die Zeilen 1 bis 4 bearbeiten Sie, um die Elemente der Aufzählung zu definieren. (Ich habe es ein "Listenmakro" genannt, weil es ein Makro ist, das eine Liste von Dingen erstellt. @Lundin teilt mir mit, dass dies eine bekannte Technik namens X-Makros ist.)

Zeile 7 definiert das innere Makro, um die eigentliche Aufzählungsdeklaration in den Zeilen 8-11 auszufüllen. Zeile 12 hebt die Definition des inneren Makros auf (nur um die Compiler-Warnung auszuschalten).

Zeile 14 definiert das innere Makro, um eine Zeichenfolgenversion des Enum-Elementnamens zu erstellen. Dann erzeugen die Zeilen 15-18 ein Array, das einen Aufzählungswert in die entsprechende Zeichenfolge konvertieren kann.

Die Zeilen 21-27 generieren eine Funktion, die eine Zeichenfolge in den Aufzählungswert konvertiert, oder geben NULL zurück, wenn die Zeichenfolge mit keiner übereinstimmt.

Dies ist etwas umständlich im Umgang mit dem 0. Element. Ich habe das in der Vergangenheit tatsächlich umgangen.

Ich gebe zu, diese Technik stört Leute, die nicht glauben wollen, dass der Präprozessor selbst so programmiert werden kann, dass er Code für Sie schreibt. Ich denke, es zeigt deutlich den Unterschied zwischen Lesbarkeit und Wartbarkeit . Der Code ist schwer zu lesen, aber wenn die Aufzählung einige hundert Elemente enthält, können Sie Elemente hinzufügen, entfernen oder neu anordnen und trotzdem sicherstellen, dass der generierte Code keine Fehler enthält.

Mike Dunlavey
quelle
"X-Makros" sind selten eine elegante Lösung für ein Problem. In diesem Fall wäre es weitaus lesbarer, die Makroelemente einfach als #define TEST_1 hello #define TEST_2 worlddann zu definieren typedef enum { TEST_1, TEST_2 } test_t;und dann eine String-Nachschlagetabelle zu erstellen, die ein Stringify-Makro verwendet: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Es gibt bereits mehrere Antworten, die auf ähnliche Lösungen hinweisen. Weitaus besser lesbar.
Lundin
@Lundin: Ich behaupte nur 1) dies funktioniert sogar mit dem primitivsten C-Compiler und 2) das Hinzufügen oder Löschen eines Elements ist eine 1-zeilige Bearbeitung.
Mike Dunlavey
Ich habe eine eigene Antwort gepostet: stackoverflow.com/a/39877228/584518 . Hoffentlich rettet es eine arme Seele vor x Makrolösungen.
Lundin
1
Ich habe deine Lösung benutzt. Ich denke es ist das Beste. Die C-Syntax ist immer noch vorhanden, damit Sie verstehen, was passiert, und die Liste nur einmal definiert wird. Sie können das 0. Element entfernen, indem Sie das Komma nach dem Eintrag in DEFINE_ENUM_ELEMENT setzen.
isgoed
1

Hier ist die Old Skool-Methode (die früher häufig in gcc verwendet wurde), bei der nur der C-Vorprozessor verwendet wird. Nützlich, wenn Sie diskrete Datenstrukturen generieren, aber die Reihenfolge zwischen ihnen konsistent halten müssen. Die Einträge in mylist.tbl können natürlich auf etwas viel Komplexeres erweitert werden.

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

Und dann 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 )
Der Herzog
quelle
1
Diese Technik heißt x Makros!
Watusimoto
0

In c ++ so:

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Јовић
quelle
0
#include <EnumString.h>

von http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C und danach

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

einfügen

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

Funktioniert einwandfrei, wenn die Werte in der Aufzählung nicht doppelt vorhanden sind.

Beispielcode zum Konvertieren eines Enum-Werts in einen String:

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

Beispielcode für genau das Gegenteil:

assert( EnumString< FORM >::To( f, str ) );
Andrey Syrokomskiy
quelle
0

Danke James für deinen Vorschlag. Es war sehr nützlich, also habe ich es umgekehrt implementiert, um einen Beitrag zu leisten.

#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
quelle
0

Um James 'Antwort zu erweitern, möchte jemand einen Beispielcode, der die Enum-Definition mit int-Wert unterstützt. Ich habe auch diese Anforderung. Hier ist mein Weg:

Das erste ist das interne Verwendungsmakro, das von FOR_EACH verwendet wird:

#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) ),

Und hier ist das Definitionsmakro:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

Wenn Sie es verwenden, möchten Sie vielleicht folgendermaßen schreiben:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

welches erweitert wird zu:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

Die Grundidee besteht darin, eine SEQ zu definieren, bei der jedes Element ein TUPLE ist, damit wir einen zusätzlichen Wert für das Enum-Member setzen können. Überprüfen Sie in der FOR_EACH-Schleife das Element TUPLE size. Wenn die Größe 2 ist, erweitern Sie den Code auf KEY = VALUE. Andernfalls behalten Sie einfach das erste Element von TUPLE bei.

Da es sich bei der Eingabe-SEQ tatsächlich um TUPLEs handelt. Wenn Sie also STRINGIZE-Funktionen definieren möchten, müssen Sie möglicherweise zuerst die Eingabe-Enumeratoren vorverarbeiten. Hier ist das Makro, um den Job auszuführen:

#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))

Das Makro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQbehält nur das erste Element in jedem TUPLE bei und konvertiert später in SEQ. Ändern Sie jetzt James 'Code. Sie haben die volle Leistung.

Meine Implementierung ist möglicherweise nicht die einfachste. Wenn Sie also keinen sauberen Code finden, verwenden Sie meinen als Referenz.

Howard Gong
quelle
0

Saubere, sichere Lösung in reinem Standard 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;
}

Ausgabe

0 hello
1 world
1 world

Begründung

Bei der Lösung des Kernproblems "Enum-Konstanten mit entsprechenden Zeichenfolgen haben" stellt ein vernünftiger Programmierer die folgenden Anforderungen:

  • Vermeiden Sie Code-Wiederholungen ("DRY" -Prinzip).
  • Der Code muss skalierbar, wartbar und sicher sein, auch wenn Elemente innerhalb der Aufzählung hinzugefügt oder entfernt werden.
  • Der gesamte Code sollte von hoher Qualität sein: leicht zu lesen, leicht zu warten.

Die erste Anforderung und vielleicht auch die zweite kann mit verschiedenen chaotischen Makrolösungen wie dem berüchtigten "x-Makro" -Trick oder anderen Formen der Makromagie erfüllt werden. Das Problem bei solchen Lösungen ist, dass sie Ihnen ein völlig unlesbares Durcheinander mysteriöser Makros hinterlassen - sie erfüllen nicht die oben genannte dritte Anforderung.

Das einzige, was hier benötigt wird, ist eine String-Nachschlagetabelle, auf die wir zugreifen können, indem wir die Variable enum als Index verwenden. Eine solche Tabelle muss natürlich direkt der Aufzählung entsprechen und umgekehrt. Wenn einer von ihnen aktualisiert wird, muss auch der andere aktualisiert werden, sonst funktioniert es nicht.


Erläuterung des Codes

Angenommen, wir haben eine Aufzählung wie

typedef enum
{
  hello,
  world
} test_t;

Dies kann in geändert werden

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Mit dem Vorteil, dass diese Makrokonstanten jetzt an anderer Stelle verwendet werden können, um beispielsweise eine String-Nachschlagetabelle zu generieren. Das Konvertieren einer Vorprozessorkonstante in eine Zeichenfolge kann mit einem "stringify" -Makro erfolgen:

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

Und das ist es. Mit using erhalten hellowir die Enum-Konstante mit dem Wert 0. Mit using erhalten test_str[hello]wir die Zeichenfolge "hello".

Damit die Aufzählung und die Nachschlagetabelle direkt übereinstimmen, müssen wir sicherstellen, dass sie genau die gleiche Anzahl von Elementen enthalten. Wenn jemand den Code pflegen und nur die Aufzählung und nicht die Nachschlagetabelle ändern würde oder umgekehrt, funktioniert diese Methode nicht.

Die Lösung besteht darin, die Aufzählung zu haben, um Ihnen zu sagen, wie viele Elemente es enthält. Hierfür gibt es einen häufig verwendeten C-Trick. Fügen Sie einfach am Ende ein Element hinzu, das nur den Zweck erfüllt, zu bestimmen, wie viele Elemente die Aufzählung enthält:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Jetzt können wir zur Kompilierungszeit überprüfen, ob die Anzahl der Elemente in der Aufzählung so hoch ist wie die Anzahl der Elemente in der Nachschlagetabelle, vorzugsweise mit einer statischen C11-Zusicherung:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(Es gibt hässliche, aber voll funktionsfähige Möglichkeiten, statische Asserts auch in älteren Versionen des C-Standards zu erstellen, wenn jemand darauf besteht, Dinosaurier-Compiler zu verwenden. C ++ unterstützt auch statische Asserts.)


Nebenbei bemerkt können wir in C11 auch eine höhere Typensicherheit erzielen, indem wir das Stringify-Makro ändern:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( intweil Aufzählungskonstanten tatsächlich vom Typ sind int, nicht test_t)

Dadurch wird verhindert, dass Code wie STRINGIFY(random_stuff)kompiliert wird.

Lundin
quelle
Ich verstehe, was Sie sagen, aber der Punkt bleibt. Die typischen vorhersehbaren Änderungen sollten nur minimale Änderungen erfordern (wie 1 Zeile). (Ich denke, das ist der Grund für DRY.) Wenn also die Größe der Aufzählung 500 beträgt und Sie ein neues Element in die Mitte einfügen möchten (oder entfernen / umbenennen / tauschen möchten), wie viele Codezeilen müssen Sie? ändern, und wie viel Überprüfung müssen Sie tun, um sicherzugehen, dass Sie keinen Fehler gemacht haben? Es kann auch andere Codeteile geben, die für jedes Element der Liste etwas Einheitliches bewirken.
Mike Dunlavey
Vielen Dank, dass Sie mir mitgeteilt haben, dass diese X-Makros genannt werden . Das wusste ich nicht. Was ich nicht sehe, sind Leute, die sie im Allgemeinen verunglimpfen.
Mike Dunlavey
@MikeDunlavey Unabhängig von der Größe der Aufzählung müssen Sie genau 3 Zeilen ändern: Fügen Sie a hinzu #define, fügen Sie einen Verweis auf die Definition hinzu, die in der Aufzählungsdeklaration und in der Nachschlagetabelle definiert ist. Wenn Sie beim Hinzufügen dieser Zeilen Fehler machen würden, wird das Programm nicht kompiliert. Die Nummern, die ich zu den Bezeichnern hinzugefügt habe, sind keineswegs obligatorisch. Sie können auch schreiben #define APPLES hellound #define ORANGES worldfolgen typedef enum { APPES, ORANGES, TEST_N } test_t;und so weiter.
Lundin
@MikeDunlavey In Bezug auf X-Makros sind die Argumente gegen sie dieselben wie die Argumente gegen funktionsähnliche Makros. Sie müssen nicht lange suchen, um eine Menge sehr berechtigter Kritik an funktionsähnlichen Makros zu finden.
Lundin
0

Was ich gemacht habe, ist eine Kombination aus dem, was ich hier und in ähnlichen Fragen auf dieser Seite gesehen habe. Ich habe dies für Visual Studio 2013 gemacht. Ich habe es nicht mit anderen Compilern getestet.

Zunächst definiere ich eine Reihe von Makros, die die Tricks ausführen.

// 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

Definieren Sie als Nächstes ein einzelnes Makro, das die Aufzählungsklasse und die Funktionen zum Abrufen der Zeichenfolgen erstellt.

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

Jetzt wird es wirklich einfach, einen Aufzählungstyp zu definieren und Zeichenfolgen dafür zu haben. Alles was Sie tun müssen ist:

ENUM(MyEnumType, A, B, C);

Die folgenden Zeilen können zum Testen verwendet werden.

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

Dies wird Folgendes ausgeben:

3
A
B
C
A
B
C
A
B
C

Ich glaube, es ist sehr sauber und einfach zu bedienen. Es gibt einige Einschränkungen:

  • Sie können den Enum-Mitgliedern keine Werte zuweisen.
  • Die Werte des Enum-Mitglieds werden als Index verwendet, aber das sollte in Ordnung sein, da alles in einem einzigen Makro definiert ist.
  • Sie können damit keinen Aufzählungstyp innerhalb einer Klasse definieren.

Wenn Sie das umgehen können. Ich denke, besonders wie man es benutzt, das ist schön und schlank. Vorteile:

  • Einfach zu verwenden.
  • Kein String-Splitting zur Laufzeit erforderlich.
  • Zum Zeitpunkt der Kompilierung sind separate Zeichenfolgen verfügbar.
  • Leicht zu lesen. Der erste Satz von Makros benötigt möglicherweise eine zusätzliche Sekunde, ist aber nicht wirklich kompliziert.
jokr
quelle
0

Eine saubere Lösung für dieses Problem wäre:

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

Das Gute an dieser Lösung ist, dass sie einfach ist und der Aufbau der Funktion auch einfach durch Kopieren und Ersetzen erfolgen kann. Beachten Sie, dass diese Lösung möglicherweise CPU-intensiv wird, wenn Sie viele Konvertierungen durchführen und Ihre Aufzählung zu viele mögliche Werte enthält.

Ali Alidoust
quelle
0

Ich bin etwas spät dran, aber hier ist meine Lösung mit g ++ und nur Standardbibliotheken. Ich habe versucht, die Verschmutzung durch Namespaces zu minimieren und die Notwendigkeit zu beseitigen, Enum-Namen erneut einzugeben.

Die Header-Datei "my_enum.hpp" lautet:

#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]]);                                \
    }                                                                   \
}

Anwendungsbeispiel:

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

Dies wird Folgendes ausgeben:

MERCURY
EARTH

Sie müssen alles nur einmal definieren, Ihr Namespace sollte nicht verschmutzt sein und die gesamte Berechnung wird nur einmal durchgeführt (der Rest sind nur Suchvorgänge). Sie erhalten jedoch nicht die Typensicherheit von Enum-Klassen (es handelt sich immer noch um kurze Ganzzahlen). Sie können den Enums keine Werte zuweisen. Sie müssen Enums an einer Stelle definieren, an der Sie Namespaces definieren können (z. B. global).

Ich bin mir nicht sicher, wie gut die Leistung ist oder ob es eine gute Idee ist (ich habe C vor C ++ gelernt, damit mein Gehirn immer noch so funktioniert). Wenn jemand weiß, warum dies eine schlechte Idee ist, können Sie darauf hinweisen.

Alias ​​Fakename
quelle
0

Es ist 2017, aber die Frage ist noch am Leben

Noch ein anderer Weg:

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

Ausgänge:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage
eungenue
quelle
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");     \
    }

Dies ist eine ausgearbeitete erweiterte Enum-Version für Klassen. Sie fügt keinen anderen Enum-Wert als den angegebenen hinzu.

Verwendung: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)

Michal Turlik
quelle
0

Ich brauchte dies, um in beide Richtungen zu arbeiten UND ich bettete meine Aufzählungen häufig in eine enthaltende Klasse ein, und so begann ich mit der Lösung von James McNellis ganz oben in diesen Antworten, aber ich machte diese Lösung. Beachten Sie auch, dass ich lieber eine Aufzählungsklasse als nur eine Aufzählung bevorzuge, was die Antwort etwas erschwert.

#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)

Um es in einer Klasse zu verwenden, können Sie Folgendes tun:

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

Und ich habe einen CppUnit-Test geschrieben, der zeigt, wie man ihn benutzt:

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

Sie müssen auswählen, welches Makro verwendet werden soll, entweder DEFINE_ENUMERATION oder DEFINE_ENUMERATION_INSIDE_CLASS. Sie werden sehen, dass ich Letzteres beim Definieren von ComponentStatus :: Status verwendet habe, aber Ersteres beim Definieren von Status. Der Unterschied ist einfach. Innerhalb einer Klasse stelle ich die to / from-Methoden als "statisch" voran und wenn nicht in einer Klasse, verwende ich "inline". Triviale Unterschiede, aber notwendig.

Leider glaube ich nicht, dass es einen sauberen Weg gibt, dies zu vermeiden:

const char * valueStr = ComponentStatus::ToString(value);

Obwohl Sie nach Ihrer Klassendefinition manuell eine Inline-Methode erstellen können, die einfach mit der Klassenmethode verkettet ist, können Sie Folgendes tun:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
Joseph Larson
quelle
0

Meine eigene Antwort, ohne Boost - mit meinem eigenen Ansatz ohne starke Definitionsmagie, und diese Lösung hat die Einschränkung, dass sie keinen bestimmten Aufzählungswert definieren kann.

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

Die neueste Version finden Sie auf Github hier:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

TarmoPikaro
quelle
0

Es gibt viele andere Antworten darauf, aber ich denke, ein besserer Weg ist es, C ++ 17-Funktionen zu verwenden und constexpr zu verwenden, damit die Übersetzungen zur Kompilierungszeit erstellt werden. Dies ist typsicher und wir müssen uns nicht mit Makros herumschlagen. Siehe unten:

//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());
    }

}

Dies kann dann einfach verwendet werden, so dass beim Kompilieren Zeichenfolgenschlüsselfehler erkannt werden:

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

Der Code ist ausführlicher als einige andere Lösungen, aber wir können die Konvertierung von Enum in String und die Konvertierung von String in Enum zur Kompilierungszeit problemlos durchführen und Typfehler erkennen. Mit einigen der zukünftigen C ++ 20-Funktionen kann dies wahrscheinlich etwas vereinfacht werden.

Marius
quelle