it-swarm.com.de

Wie konvertiert man eine Enummentyp-Variable in einen String?

Wie kann man printf dazu bringen, 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 muss eine Zeichenfolge "Linux" zeigen, keine ganze Zahl.

Ich gehe davon aus, dass ich zunächst ein durch Werte indiziertes String-Array erstellen muss. Aber ich weiß nicht, ob das der schönste Weg ist. Ist das überhaupt möglich?

92
psihodelia

Es gibt wirklich keine schöne Möglichkeit, dies zu tun. Richten Sie einfach ein von der Aufzählung indiziertes Array von Strings ein.

Wenn Sie viele Ausgaben ausführen, können Sie einen Operator << definieren, der einen Enum-Parameter verwendet und die Suche für Sie übernimmt.

55
Bo Persson

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

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 sieht wie folgt aus:

#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 und dann eine ToString-Funktion, die ein Objekt dieses Typs übernimmt und den Namen des Enumerators als Zeichenfolge zurückgibt (diese Implementierung erfordert aus naheliegenden Gründen, dass die Enumeratoren eindeutigen Werten zugeordnet sind). 

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

Als Verwendungsbeispiel würde Ihre OS_type-Enumeration folgendermaßen definiert:

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

Während das Makro zunächst so aussieht, als sei es eine Menge Arbeit, und die Definition von OS_type wirkt ziemlich fremd. Denken Sie jedoch daran, dass Sie das Makro einmal schreiben müssen. Dann können Sie es für jede Aufzählung verwenden. Sie können zusätzliche Funktionen hinzufügen (z. B. eine String-Form für die Enum-Konvertierung), und dies löst das Wartungsproblem vollständig, da Sie die Namen nur einmal angeben müssen, wenn Sie das Makro aufrufen.

Die Aufzählung kann dann verwendet werden, als wäre sie normalerweise definiert:

#include <iostream>

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

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

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

119
James McNellis

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ählungsdefinition 

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

Rufen Sie mit an 

GetStringOs_type(winblows);

Aus hier . Wie cool ist das ? :) 

25
Reno

Das Problem bei C-Enums ist, dass es sich nicht um einen eigenen Typ handelt, wie er in C++ vorliegt. Eine Enumeration in C ist eine Möglichkeit, Bezeichner auf ganzzahlige Werte abzubilden. Nur das. Aus diesem Grund ist ein Aufzählungswert mit ganzzahligen Werten austauschbar.

Wie Sie richtig raten, ist es eine gute Möglichkeit, eine Zuordnung zwischen dem Aufzählungswert und einer Zeichenfolge zu erstellen. Zum Beispiel:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
7
Andrew

Dieses einfache Beispiel hat für mich 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";
}
6
fr4nk

Ich habe die Lösungen James ' , Howards und Éder's kombiniert und eine allgemeinere Implementierung erstellt:

  • die Darstellung von int-Werten und benutzerdefinierten Zeichenfolgen kann optional für jedes Enumerationselement 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;
}
5
PolarBear

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

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

Es gibt viele gute Antworten, aber ich dachte, einige Leute könnten meine nützlich finden. Ich mag es, weil die Schnittstelle, mit der Sie das Makro definieren, so einfach ist, wie es nur geht. 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 aus verschiedenen Orten online abgerufen, sodass ich nicht für alles die Ehre bekomme, aber ich denke, es ist einzigartig genug, um eine neue Antwort zu erhalten.

Erstellen Sie zuerst eine Header-Datei ... nennen Sie sie EnumMacros.h oder ähnliches, und fügen Sie Folgendes hinzu:

// 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 wäre die Ausgabe >> Der Wert von 'Apple' ist: 2 von 4

Genießen!

4
Ph0t0n

Für C99 gibt es P99_DECLARE_ENUM in P99 , mit dem Sie enum einfach wie folgt deklarieren können:

P99_DECLARE_ENUM(color, red, green, blue);

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

3
Jens Gustedt

Vorausgesetzt, dass Ihre Aufzählung bereits definiert ist, 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 Enumeration geändert wird, müssen Sie Paare aus Array-Paaren [] hinzufügen oder entfernen. Ich denke, es ist der eleganteste Weg, eine Zeichenfolge aus enum in C++ zu erhalten.

3
Vladimir

Hast du das probiert:

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

Mit dem stringify()-Makro kann jeder Text in Ihrem Code in eine Zeichenfolge umgewandelt werden, jedoch nur der genaue Text zwischen den Klammern. Es werden keine Variablen-Dereferenzierungen oder Makrosubstitutionen oder andere Aktivitäten durchgeführt.

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

3
M.Ali

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;
  }
2
Marco Amagliani

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)
2
y2k1234

Eine weitere Verspätung 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 es einfacher ist, darüber zu sprechen.) Die Zeilen 1-4 sind das, was Sie bearbeiten, um die Elemente der Aufzählung zu definieren ..__ ein Makro, das eine Liste von Dingen erstellt. @Lundin weist darauf hin, dass dies eine bekannte Technik ist, die X-Makros genannt wird.)

Zeile 7 definiert das innere Makro, um die eigentliche Enumendeklaration in den Zeilen 8 bis 11 auszufüllen. In Zeile 12 wird das innere Makro nicht definiert (nur um die Compiler-Warnung auszuschalten).

Zeile 14 definiert das innere Makro, um eine String-Version des Enum-Elementnamens zu erstellen. __ Dann erzeugen Zeile 15-18 ein Array, das einen Enum-Wert in den entsprechenden String konvertieren kann.

Die Zeilen 21-27 generieren eine Funktion, die einen String in den Aufzählungswert konvertiert oder NULL zurückgibt, wenn der String mit keinem übereinstimmt.

Dies ist ein wenig umständlich in der Art und Weise, wie es mit dem 0. Element umgeht.

Ich gebe zu, diese Technik stört Leute, die nicht glauben wollen, dass der Präprozessor selbst programmiert werden kann, um Code für Sie zu schreiben. Ich denke, es veranschaulicht den Unterschied zwischen Readability und Maintenanceability ..__ Der Code ist schwer zu lesen...... Wenn die Aufzählung jedoch 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.

1
Mike Dunlavey

Etwas 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
    .................
}
1
OneOfOne

Hier ist die Old Skool-Methode (die in gcc ausgiebig verwendet wurde) nur mit dem C-Vorprozessor. Nützlich, wenn Sie diskrete Datenstrukturen generieren, aber die Reihenfolge zwischen ihnen konstant halten müssen. Die Einträge in mylist.tbl können natürlich um etwas 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 )
1
TheDuke

Meine eigene Präferenz ist es, sowohl das wiederholte Tippen als auch schwer verständliche Makros zu minimieren und zu vermeiden, Makrodefinitionen in den allgemeinen Compiler-Bereich einzufügen.

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 die #undef des Makros, sobald wir damit fertig sind.

1
gerardw
#include <EnumString.h>

von http://www.codeproject.com/Articles/42035/Enum-nach-String-und-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 gut, wenn die Werte in der Aufzählung nicht doppelt vorhanden sind.

Beispielcode zum Konvertieren eines Aufzählungswerts in eine Zeichenfolge:

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

Beispielcode für das Gegenteil:

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

Ich brauchte diese Funktion, um in beide Richtungen zu arbeiten UND ich habe häufig mein Enum in eine umfassende Klasse eingebettet, und so begann ich mit der Lösung von James McNellis, ganz oben bei diesen Antworten, aber ich habe diese Lösung gefunden. Beachten Sie auch, ich bevorzuge die Enum-Klasse und nicht nur die Enumeration, was die Antwort etwas komplizierter macht.

#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 ich habe das erstere beim Definieren von Status verwendet. Der Unterschied ist einfach. Innerhalb einer Klasse füge ich die to/from-Methoden als "statisch" vor 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);

sie könnten zwar nach Ihrer Klassendefinition manuell eine Inline-Methode erstellen, die einfach an die Klassenmethode gekettet wird.

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

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

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 ausgegeben:

MERCURY
EARTH

Sie müssen nur alles einmal definieren, Ihr Namespace sollte nicht verschmutzt werden, und die gesamte Berechnung wird nur einmal ausgeführt (der Rest ist nur eine Suche). Sie erhalten jedoch nicht die Typensicherheit von Enumenklassen (sie sind immer noch nur kurze Ganzzahlen), Sie können den Enummen keine Werte zuweisen. Sie müssen Enums irgendwo definieren, wo 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 es gerne wissen.

0
Alias Fakename

Um James 'Antwort zu erweitern, möchte jemand einen Beispielcode zur Unterstützung der Enumefinition mit int-Wert haben. Ich habe auch diese Anforderung, also hier ist mein Weg:

Das erste ist das Makro für den internen Gebrauch, 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 Define-Makro:

#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 so schreiben:

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

was sich erweitern wird auf:

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

Die Grundidee besteht darin, einen SEQ zu definieren, bei dem jedes Element ein Tuple ist, sodass wir einen zusätzlichen Wert für das Enumerationsmitglied angeben können. Überprüfen Sie in FOR_EACH-Schleife das Element Tuple-Größe. Wenn die Größe 2 ist, erweitern Sie den Code auf KEY = VALUE. Andernfalls behalten Sie nur das erste Element von Tuple bei.

Da die Eingabe-SEQ eigentlich TUPLEs ist. Wenn Sie also STRINGIZE-Funktionen definieren möchten, müssen Sie möglicherweise zuerst die Eingabe-Enumeratoren vorverarbeiten.

#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_SEQ wird nur das erste Element in jedem Tuple beibehalten und später in SEQ konvertieren. Jetzt wird der Code von James geändert, und Sie haben die volle Leistung.

Meine Implementierung ist vielleicht nicht die einfachste, wenn Sie also keinen sauberen Code finden, meinen als Referenz.

0
Howard Gong

Es ist 2017, aber die Frage ist immer noch lebendig

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
0
eungenue

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 die Erstellung der Funktion auch einfach durch Kopieren und Ersetzen erfolgen kann. Wenn Sie viele Konvertierungen durchführen und Ihre Aufzählung zu viele mögliche Werte hat, kann diese Lösung CPU-intensiv werden.

0
Ali Alidoust

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 "Konstanten mit entsprechenden Zeichenfolgen aufstellen" stellt ein vernünftiger Programmierer folgende 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 lesbar, leicht zu warten.

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

Das einzige, was hier benötigt wird, ist tatsächlich eine String-Lookup-Tabelle, auf die wir zugreifen können, indem wir die enum-Variable 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 er nicht.


Erklärung des Codes

Angenommen, wir haben eine Aufzählung

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-Lookup-Tabelle 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 hello erhalten wir die Enumenkonstante mit dem Wert 0. Mit test_str[hello] erhalten wir die Zeichenfolge "hallo".

Um die Enumeration und die Nachschlagetabelle direkt miteinander in Einklang zu bringen, müssen wir sicherstellen, dass sie die gleiche Menge an Elementen enthalten. Wenn jemand den Code beibehält und nur die Aufzählung und nicht die Nachschlagetabelle oder umgekehrt ändert, funktioniert diese Methode nicht.

Die Lösung besteht darin, über die Enumeration zu verfügen, wie viele Elemente darin enthalten sind. Dazu gibt es einen häufig verwendeten C-Trick. Fügen Sie am Ende einfach ein Element hinzu, das nur den Zweck erfüllt, zu sagen, 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 Kompilierzeit überprüfen, ob die Anzahl der Elemente in der Aufzählung der Anzahl der Elemente in der Nachschlagetabelle entspricht, 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 auch hässliche, aber voll funktionsfähige Möglichkeiten, statische Asserts in älteren Versionen des C-Standards zu erstellen, falls jemand darauf besteht, Dinosaurier-Compiler zu verwenden. Wie für C++ unterstützt er auch statische Asserts.)


Als Randbemerkung können wir in C11 auch eine höhere Typsicherheit erreichen, indem Sie das Stringify-Makro ändern:

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

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

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

0
Lundin

Was ich gemacht habe, ist eine Kombination aus dem, was ich hier und in ähnlichen Fragen auf dieser Website gesehen habe. Ich habe gemacht, dass dies Visual Studio 2013 ist. 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

Als Nächstes definieren Sie ein einzelnes Makro, das die Enum-Klasse 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;                                                                                                  \
    }

Nun ist es sehr einfach, einen Aufzählungstyp zu definieren und Strings 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 ausgegeben:

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 Aufzählungsmitgliedern keine Werte zuweisen.
  • Die Werte der Aufzählungsmitglieder werden als Index verwendet. Dies sollte jedoch in Ordnung sein, da alles in einem einzigen Makro definiert ist.
  • Sie können es nicht verwenden, um einen Aufzählungstyp innerhalb einer Klasse zu definieren.

Wenn du das umgehen kannst. Ich denke, vor allem, wie man es benutzt, das ist schön und schlank. Vorteile:

  • Einfach zu verwenden.
  • Es ist keine Stringaufteilung zur Laufzeit erforderlich.
  • Zur Kompilierzeit stehen separate Zeichenfolgen zur Verfügung.
  • Leicht zu lesen. Der erste Satz von Makros benötigt möglicherweise eine zusätzliche Sekunde, ist aber nicht so kompliziert.
0
jokr

Ich habe eine Antwort hinzugefügt, die keine Aufzählungswerte unterstützte. Jetzt wurde eine Unterstützung hinzugefügt, die auch die Aufzählungswertzuweisung unterstützt. Wie in der vorherigen Lösung wird hier Minimum Define Magic verwendet.

Hier ist die Header-Datei:

#pragma once
#include <string>
#include <map>
#include <regex>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name>: public EnumReflectBase<##name> {         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // 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...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

Und hier ist ein Beispiel für eine Testanwendung:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

Die aktualisierte Version derselben Header-Datei wird hier aufbewahrt:

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

0
TarmoPikaro
#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 erweiterte Enum-Version der Klasse ... Sie fügt keinen anderen Enumerationswert als den angegebenen hinzu.

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

0
Michal Turlik

Meine eigene Antwort, ohne Boost - mit meiner eigenen Herangehensweise ohne starke Definitionsmagie, und diese Lösung hat die Einschränkung, dass es nicht möglich ist, einen bestimmten Enum-Wert zu definieren.

#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 ist auf Github hier zu finden:

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

0
TarmoPikaro

In C++ wie folgt:

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";
}
0
BЈовић

Danke James für deinen Vorschlag. Es war sehr nützlich, also habe ich den umgekehrten Weg eingeschlagen, um etwas beizutragen.

#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;
}
0
Éder