webentwicklung-frage-antwort-db.com.de

Einfache Möglichkeit, Variablen von Aufzählungstypen als Zeichenfolge in C zu verwenden?

Folgendes versuche ich zu tun: 

typedef enum { ONE, TWO, THREE } Numbers;

Ich versuche, eine Funktion zu schreiben, die einen Umschaltfall ähnlich dem folgenden ausführt: 

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Gibt es eine Möglichkeit, sie in jedem Fall zu definieren, indem Sie die enum-Variable verwenden, wie ich es oben versuche?

83
zxcv

Es gibt keine integrierte Lösung. Am einfachsten ist dies mit einem Array von char*, bei dem der int-Wert der Enumeration zu einem String mit dem beschreibenden Namen dieser Enumeration indexiert wird. Wenn Sie eine enum mit geringer Dichte haben (eine, die nicht bei 0 beginnt oder Lücken in der Nummerierung aufweist), wobei einige der int-Zuordnungen hoch genug sind, um ein Array-basiertes Mapping unpraktisch zu machen, können Sie stattdessen eine Hash-Tabelle verwenden.

13
sk.

Die Technik aus Etwas sowohl als C-Bezeichner als auch als String machen? kann hier verwendet werden.

Wie bei solchen Präprozessoren üblich, kann das Schreiben und Verstehen des Präprozessorteils schwierig sein und das Übergeben von Makros an andere Makros sowie das Verwenden von # und ##-Operatoren umfassen. Die Verwendung von # und ## ist jedoch sehr einfach. Ich finde diesen Stil sehr nützlich für lange Aufzählungen, bei denen die zweimalige Aufrechterhaltung derselben Liste sehr mühsam sein kann.

Werkscode - nur einmal eingegeben, normalerweise in der Kopfzeile ausgeblendet:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Fabrik verwendet

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

Die Technik kann leicht erweitert werden, so dass XX-Makros mehr Argumente akzeptieren, und Sie können auch weitere Makros vorbereitet haben, um XX für unterschiedliche Anforderungen zu ersetzen, ähnlich den drei, die ich in diesem Beispiel bereitgestellt habe.

Vergleich zu X-Macros mit #include/#define/#undef

Dies ist zwar ähnlich wie bei anderen X-Macros, aber ich denke, dass diese Lösung eleganter ist, da #undefing nichts erforderlich ist, was es Ihnen erlaubt, mehr komplizierte Dinge zu verbergen, die in der Fabrik die Headerdatei - die Headerdatei - ist Wenn Sie ein neues Enum definieren möchten, berühren Sie dies gar nicht. Daher ist die neue Enumendefinition viel kürzer und sauberer.

61
Suma
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever
61
Bill Forster

Es gibt definitiv einen Weg, dies zu tun - use X() Makros . Diese Makros verwenden den Präprozessor C, um aus einer Liste von Quelldaten Aufzählungen, Arrays und Codeblöcke zu erstellen. Sie müssen nur neue Elemente zu der # Definition hinzufügen, die das Makro X() enthält. Die switch-Anweisung wird automatisch erweitert.

Ihr Beispiel kann wie folgt geschrieben werden:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Es gibt effizientere Möglichkeiten (z. B. Verwendung von X-Makros zum Erstellen eines String-Arrays und eines Enum-Index), dies ist jedoch die einfachste Demo.

13
JayG

Ich weiß, dass Sie ein paar solide Antworten haben, aber wissen Sie über den # -Operator im C-Präprozessor?

Sie können dies tun:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}
8
plinth

KUSS. Sie werden mit Ihren Aufzählungen alle möglichen anderen Dinge tun. Warum sollte das Drucken anders sein? Einen Fall in der Druckroutine zu vergessen, ist keine große Sache, wenn man bedenkt, dass es ungefähr 100 andere Orte gibt, an denen man einen Fall vergessen kann. Kompilieren Sie einfach -Wall, die vor nicht erschöpfenden Fallkämpfen warnt. Verwenden Sie nicht "default", da dies den Schalter erschöpfend macht und Sie keine Warnungen erhalten. Lassen Sie stattdessen den Schalter beenden und behandeln Sie den Standardfall wie folgt ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}
6

C oder C++ bietet diese Funktionalität nicht, obwohl ich sie oft gebraucht habe.

Der folgende Code funktioniert, obwohl er am besten für nicht spärliche Enumerationen geeignet ist.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Mit nicht spärlich meine ich nicht die Form

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

denn das hat große Lücken.

Der Vorteil dieser Methode besteht darin, dass die Definitionen der Aufzählungen und Strings einander nahekommen. Wenn Sie eine switch-Anweisung in einer Funktion haben, werden sie durchbrochen. Dies bedeutet, dass Sie weniger wahrscheinlich ohne den anderen wechseln.

5
paxdiablo

Try Konvertieren von C++ - Enums in Strings . Die Kommentare weisen Verbesserungen auf, die das Problem lösen, wenn Aufzählungselemente beliebige Werte haben.

4
Bob Nadler

Die Verwendung von boost :: preprozessor ermöglicht eine elegante Lösung wie die folgende:

Schritt 1: Fügen Sie die Headerdatei ein:

#include "EnumUtilities.h"

Schritt 2: Deklarieren Sie das Aufzählungsobjekt mit der folgenden Syntax:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Schritt 3: Verwenden Sie Ihre Daten:

Die Anzahl der Elemente abrufen:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Die zugehörige Zeichenfolge erhalten:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Abrufen des Aufzählungswerts aus der zugehörigen Zeichenfolge:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Das sieht sauber und kompakt aus, ohne dass zusätzliche Dateien eingefügt werden müssen ..__ Der Code, den ich in EnumUtilities.h geschrieben habe, ist folgender:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Es gibt einige Einschränkungen, d. H. Die von boost :: preprozessor. In diesem Fall darf die Liste der Konstanten nicht größer als 64 Elemente sein.

Nach der gleichen Logik könnten Sie auch an eine spärliche Enumeration denken:

#define EnumName(Tuple)                 BOOST_PP_Tuple_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_Tuple_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

In diesem Fall lautet die Syntax:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

Die Verwendung ist ähnlich wie oben (minus der Funktion eName ## 2Enum, die Sie versuchen könnten, aus der vorherigen Syntax zu extrapolieren).

Ich habe es auf Mac und Linux getestet. Beachten Sie jedoch, dass der boost :: preprozessor möglicherweise nicht vollständig portierbar ist.

4
Giacomo M.

Durch das Zusammenführen einiger Techniken hier habe ich die einfachste Form gefunden:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

Wenn Sie gcc verwenden, können Sie Folgendes verwenden:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

Dann rufen Sie einfach an

enum_to_string_map[enum1]
2
janisj

Die Ideen finden Sie unter Mu Dynamics Research Labs - Blog Archiv . Ich habe dies früher in diesem Jahr gefunden - ich vergesse den genauen Kontext, in dem ich darauf gestoßen bin - und habe es in diesen Code übernommen. Wir können über die Vorteile eines E-Zusatzes an der Front debattieren. Es ist auf das spezifische Problem anwendbar, jedoch nicht Teil einer allgemeinen Lösung. Ich habe dies in meinem Ordner "Vignetten" verstaut - dort bewahre ich interessante Codefetzen auf, falls ich sie später möchte. Es ist mir peinlich zu sagen, dass ich keine Notiz darüber hatte, woher diese Idee damals kam.

Header: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Beispielquelle:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

Nicht unbedingt die weltweit sauberste Verwendung des C-Vorprozessors - verhindert jedoch, dass das Material mehrmals ausgeschrieben wird.

1
1
OJW

Wenn der Aufzählungsindex 0-basiert ist, können Sie die Namen in ein Array von char * eingeben und mit dem Aufzählungswert indizieren.

0
Colen

Ich habe eine einfache Klasse streamable_enum erstellt, die die Stream-Operatoren << und >> verwendet und auf dem std::map<Enum, std::string> basiert: 

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Verwendungszweck:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}
0
Robert Husák

Da ich aus den üblichen Gründen keine Makros verwende, habe ich eine eingeschränkte Makrolösung verwendet, die den Vorteil hat, dass das Enum-Deklarationsmakro frei bleibt. Zu den Nachteilen gehört, dass Sie die Makrodefinition für jede Aufzählung kopieren und explizit einen Makroaufruf hinzufügen müssen, wenn Sie der Aufzählung Werte hinzufügen.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
0
gerardw

Ich dachte, eine Lösung wie Boost.Fusion One zur Anpassung von Strukturen und Klassen wäre Nice, sie hatten sogar die Möglichkeit, Enumerationen als Fusionssequenz zu verwenden.

Also habe ich nur ein paar kleine Makros erstellt, um den Code zum Drucken der Aufzählungen zu generieren. Dies ist nicht perfekt und hat mit Boost.Fusion generierten Boilerplate-Code nichts zu sehen, kann aber wie die Boost Fusion-Makros verwendet werden. Ich möchte wirklich die Typen generieren, die Boost.Fusion für die Integration in diese Infrastruktur benötigt, um Namen von Strukturmitgliedern drucken zu können. Dies wird jedoch später geschehen, da dies nur Makros sind:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

Die alte Antwort unten ist ziemlich schlecht, bitte verwenden Sie das nicht. :)

Alte Antwort:

Ich habe nach einem Weg gesucht, der dieses Problem löst, ohne die Deklarationssyntax der Aufzählung zu sehr zu ändern. Ich bin zu einer Lösung gekommen, die den Präprozessor verwendet, um eine Zeichenfolge aus einer stringifizierten Enumendeklaration abzurufen.

Ich kann nicht-spärliche Aufzählungen wie folgt definieren:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

Und ich kann auf verschiedene Arten mit ihnen interagieren:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Basierend auf den folgenden Definitionen:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Wenn ich Unterstützung für spärliches Enum brauche und wenn ich mehr Zeit habe, werde ich die Implementierungen von to_string und from_string mit boost :: xpressive verbessern, was jedoch aufgrund der wichtige Vorlagen ausgeführt und die generierte ausführbare Datei ist wahrscheinlich wirklich größer. Dies hat jedoch den Vorteil, dass es besser lesbar und wartungsfähig ist als dieser hässliche Code für die manuelle Manipulation von Strings.: D

Ansonsten habe ich boost :: bimap immer verwendet, um solche Zuordnungen zwischen Aufzählungswert und Zeichenfolge durchzuführen, aber es muss manuell gepflegt werden.

0
daminetreg

Hier ist eine Lösung, die Makros mit den folgenden Funktionen verwendet:

  1. schreiben Sie jeden Wert der Aufzählung nur einmal, sodass keine doppelten Listen vorhanden sind

  2. bewahren Sie die Aufzählungswerte nicht in einer separaten Datei auf, die später in # enthalten ist, damit ich sie schreiben kann, wo immer Sie möchten

  3. ersetzen Sie das Enum nicht selbst, ich möchte immer noch den Enum-Typ definieren, aber zusätzlich möchte ich in der Lage sein, jeden Enumnenamen der entsprechenden Zeichenfolge zuzuordnen (um nicht auf älteren Code zu wirken).

  4. die Suche sollte schnell sein, also vorzugsweise kein Fallbeispiel für diese riesigen Aufzählungen

https://stackoverflow.com/a/20134475/1812866

0
muqker
#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Weitere Diskussion zu dieser Methode

Präprozessor-Direktive für Neuankömmlinge

0
Ben