C ++: Enum-Wert als Text ausdrucken

86

Wenn ich so eine Aufzählung habe

enum Errors
{ErrorA=0, ErrorB, ErrorC};

Dann möchte ich auf Konsole ausdrucken

Errors anError = ErrorA;
cout<<anError;/// 0 will be printed

aber was ich will ist der text "ErrorA", kann ich es ohne if / switch machen?
Und was ist Ihre Lösung dafür?

Tiboo
quelle
Ich denke, meine Antwort ist ziemlich gut. Würde es Ihnen etwas ausmachen, einen Blick darauf zu werfen?
Xiao
2
Für C ++ 11 enum class: stackoverflow.com/questions/11421432/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
2
Aufzählung zum String: stackoverflow.com/questions/201593/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Antworten:

62

Karte verwenden:

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

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Verwenden eines Arrays von Strukturen mit linearer Suche:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        const char* str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Schalter / Gehäuse verwenden:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}
SigTerm
quelle
12
-1. Machen Sie einfach einen Switch-Case, anstatt eine Hash-Map zu verwenden. Erhöhte Komplexität ist keine gute Sache.
Simon
8
Guter Punkt. Nächstes Mal werde ich :) Aber jetzt sehe ich, dass Sie Ihren Beitrag bereits bearbeitet haben, um die Art von Funktionalität hinzuzufügen, nach der ich gesucht habe. Gut gemacht!
Simon
1
Was ist #p? Wenn ich im dritten Beispiel anstelle von enum eine enum-Klasse verwende, ist es dann möglich, nur die enum-Zeichenfolge ohne den Klassennamen abzurufen?
Rh0x
2
#pist der Präprozessor, der p stringifiziert. Der Aufruf PROCESS_VAL(ErrorA)gibt also Folgendes aus : case(ErrorA): s = "ErrorA"; break;.
Nashenas
Ich halte es nicht als optimale Lösung: Gründe: 1) Ich habe zu halten zweimal die enumWerte , die ich denke , ist ein NO-GO . 2) Wenn ich die Lösung richtig verstehe, funktioniert sie nur für eine enum.
Peter VARGA
28

Verwenden Sie ein Array oder einen Vektor von Zeichenfolgen mit übereinstimmenden Werten:

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

BEARBEITEN: Die obige Lösung ist anwendbar, wenn die Aufzählung zusammenhängend ist, dh bei 0 beginnt und keine Werte zugewiesen sind. Es wird perfekt mit der Aufzählung in der Frage funktionieren.

Um weiter zu beweisen, dass die Aufzählung nicht bei 0 beginnt, verwenden Sie:

cout << ErrorTypes[anError - ErrorA];
Igor Oks
quelle
4
Leider erlaubt uns enum, den Elementen Werte zuzuweisen. Wie gehen Sie bei der Arbeit vor, wenn Sie nicht zusammenhängende Aufzählungen haben? Zeile 'Aufzählungsstatus {OK = 0, Fehler = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000} `(damit Sie später IOErrors hinzufügen können auf den Bereich -1001-1999)
Nordic Mainframe
@ Luther: Ja, dies funktioniert nur mit zusammenhängenden Aufzählungen, die die meisten Aufzählungen sind . Falls diese Aufzählung nicht zusammenhängend ist, müssen Sie einen anderen Ansatz verwenden, z. B. Karten. Aber im Falle einer zusammenhängenden Aufzählung würde ich vorschlagen, diesen Ansatz zu verwenden und nicht zu komplizieren.
Igor Oks
2
Wenn mein Kollege einer Aufzählung NewValue hinzufügt und das ErrorTypes-Array nicht aktualisiert, liefert ErrorTypes [NewValue] was? Und wie gehe ich mit negativen Aufzählungswerten um?
Nordic Mainframe
2
@ Luther: Sie müssen ErrorTypes auf dem neuesten Stand halten. Auch hier gibt es einen Kompromiss zwischen Einfachheit und Universalität, je nachdem, was für den Benutzer wichtiger ist. Was ist das Problem mit negativen Aufzählungswerten?
Igor Oks
1
Sollte dieses Array aus Gründen der Speichereffizienz nicht statisch sein? und const für die Sicherheit?
Jonathan
15

Hier ist ein Beispiel, das auf Boost.Preprocessor basiert:

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

#define DEFINE_ENUM_FORMAT(r, data, elem)             \
  case BOOST_PP_SEQ_HEAD(elem):                       \
  return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem));


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}
Philipp
quelle
2
+1, Diese Lösung basiert nicht auf einem externen Tool wie der obigen Lua-Antwort, sondern ist reines C ++, folgt dem DRY-Prinzip und die Benutzersyntax ist lesbar (bei korrekter Formatierung. Übrigens benötigen Sie keine Backslashes bei Verwendung von DEFINE_ENUM, das etwas natürlicher aussieht, IMO)
Fabio Fracassi
3
@Fabio Fracassi: "Diese Lösung basiert nicht auf einem externen Tool" Boost ist ein externes Tool - keine Standard-C ++ - Bibliothek. Außerdem ist es etwas zu lang. Die Lösung eines Problems sollte so einfach wie möglich sein. Dieser qualifiziert sich nicht ...
SigTerm
2
Eigentlich ist es alles, was Sie tun können, um den größten Teil des Codes (tatsächlich alles außer der eigentlichen Definition) in einen einzigen Header zu setzen. Dies ist also die kürzeste Lösung, die hier vorgestellt wird. Und um das Äußere zu fördern, ja, aber weniger als ein Skript außerhalb der Sprache für die Vorverarbeitung von Teilen der Quelle, wie es das obige Lua-Skript ist. Außerdem liegt Boost so nahe am Standard, dass es in jeder C ++ - Programmier-Toolbox enthalten sein sollte. Nur IMHO natürlich
Fabio Fracassi
[Ich habe das unnötige Entweichen von Zeilenumbrüchen im Makroaufruf entfernt. Sie werden nicht benötigt: Ein Makroaufruf kann mehrere Zeilen umfassen.]
James McNellis
Das Makro DEFINE_ENUMgibt mir den Fehler, multiple definition of `format_ProgramStatus(ProgramStatus)'wenn ich versuche, es zu verwenden.
HelloGoodbye
5

Sie können einen einfacheren Pre-Prozessor-Trick verwenden, wenn Sie bereit sind, Ihre enumEinträge in einer externen Datei aufzulisten.

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

In einer Quelldatei behandeln Sie die Datei dann wie eine Include-Datei, definieren jedoch, was Sie ERROR_DEFtun möchten .

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};

static inline std::ostream & operator << (std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
    #include "errors.def"
    #undef ERROR_DEF
    default: return o << "unknown[" << e << "]";
    }
}

Wenn Sie ein Quell-Browsing-Tool (wie cscope) verwenden, müssen Sie es über die externe Datei informieren.

jxh
quelle
4

Hier gab es eine Diskussion, die helfen könnte: Gibt es eine einfache Möglichkeit, die C ++ - Aufzählung in einen String umzuwandeln?

UPDATE: Hier ist ein Skript für Lua, das einen Operator << für jede benannte Aufzählung erstellt, auf die es trifft. Dies erfordert möglicherweise einige Arbeit, damit es für die weniger einfachen Fälle funktioniert [1]:

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

Angesichts dieser Eingabe:

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

Es produziert:

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

Das ist wahrscheinlich ein Anfang für Sie.

[1] Aufzählungen in verschiedenen oder Nicht-Namespace-Bereichen, Aufzählungen mit Initialisiererausdrücken, die ein Komma enthalten usw.

Nordischer Mainframe
quelle
Ist es hier nicht Brauch, ein '-1' zu kommentieren, um dem Poster die Möglichkeit zu geben, seine Antwort zu korrigieren? Ich frage nur ..
Nordic Mainframe
2
Ich denke, die unten stehende Boost PP-Lösung (von Philip) ist besser, da die Verwendung externer Tools in Bezug auf die Wartung sehr teuer ist. aber nein -1, weil die Antwort sonst gültig ist
Fabio Fracassi
4
Das Boost PP ist auch ein Wartungsproblem, da jeder die Boost PP-Metasprache sprechen muss. Diese Sprache ist schrecklich , leicht zu brechen (normalerweise unbrauchbare Fehlermeldungen) und nur von eingeschränkter Verwendbarkeit (lua / python / perl kann Code aus beliebigem Code generieren) externe Daten). Es fügt Ihrer Abhängigkeitsliste einen Schub hinzu, der aufgrund der Projektrichtlinien möglicherweise nicht einmal zulässig ist. Es ist auch invasiv, da Sie Ihre Aufzählungen in einem DSL definieren müssen. Ihr bevorzugtes Quellcode-Tool oder Ihre Lieblings-IDE hat möglicherweise Probleme damit. Und zu guter Letzt: Sie können in der Erweiterung keinen Haltepunkt setzen.
Nordic Mainframe
4

Ich verwende ein String-Array, wenn ich eine Aufzählung definiere:

Profile.h

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;
Mark Ingram
quelle
3
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}
Vladimir Chernyshev
quelle
3

Das ist ein guter Weg,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

Drucken Sie es mit einem Array von Zeichenarrays

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

So was

std::cout << rank_txt[m_rank - 1]
MrPickles7
quelle
2
Was ist, wenn meine Aufzählung ab 2000 beginnt? Diese Lösung wird nicht funktionieren.
Sitesh
2

Sie könnten einen stl-Kartencontainer verwenden ....

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;
Adrian Regan
quelle
4
Wie ist das eine Karte besser als das switch(n) { case XXX: return "XXX"; ... }? Welches hat O (1) Lookup und muss nicht initialisiert werden? Oder ändern sich die Aufzählungen zur Laufzeit?
Nordic Mainframe
Ich stimme @Luther Blissett bei der Verwendung der switch-Anweisung (oder auch eines Funktionszeigers) zu
KedarX
1
Nun, er möchte vielleicht "Dies ist mein lieber Freund Luther ist Fehler A oder" Dies ist mein lieber Freund Adrian ist Fehler B "ausgeben. Durch die Verwendung der Karte wird auch die Abhängigkeit von iostream-Signaturen aufgehoben, sodass er sie an anderer Stelle im Internet verwenden kann Code mit String-Verkettung, zum Beispiel String x = "Hallo" + m [FehlerA] usw.
Adrian Regan
Ich bin sicher, dass std :: map viele Wenns und Schalter enthält. Ich würde dies lesen als "Wie kann ich das tun, ohne dass ich if's und switch schreibe"
Nordic Mainframe
Ich bin sicher, dass es das tut, aber es erfordert sicherlich nicht, dass Sie ein Skript in Lua schreiben, um das Problem zu lösen ...
Adrian Regan
1

Für dieses Problem mache ich eine Hilfefunktion wie folgt:

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

Die lineare Suche ist normalerweise effizienter als std::mapbei kleinen Sammlungen wie dieser.

Johan Kotlinski
quelle
1

Für diese Lösung müssen Sie keine Datenstrukturen verwenden oder eine andere Datei erstellen.

Grundsätzlich definieren Sie alle Ihre Aufzählungswerte in einem #define und verwenden sie dann im Operator <<. Sehr ähnlich zu @ jxhs Antwort.

Ideone-Link für die endgültige Iteration: http://ideone.com/hQTKQp

Vollständiger Code:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    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) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Ausgabe:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

Das Schöne daran ist, dass Sie für jeden Fehler auch Ihre eigenen benutzerdefinierten Nachrichten angeben können, wenn Sie glauben, dass Sie diese benötigen:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    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,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Ausgabe:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

Wenn Sie Ihre Fehlercodes / Beschreibungen sehr aussagekräftig gestalten möchten, möchten Sie sie möglicherweise nicht in Produktions-Builds. Es ist einfach, sie auszuschalten, damit nur der Wert gedruckt wird:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

Ausgabe:

Error: 0
Error: 1
Error: 2

In diesem Fall wäre das Auffinden der Fehlernummer 525 eine PITA. Wir können die Zahlen in der Anfangsaufzählung wie folgt manuell angeben:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

Ausgabe:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh
Xiao
quelle
0

Wie wäre es damit?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

etc ... Ich weiß, dass dies ein sehr ausgeklügeltes Beispiel ist, aber ich denke, dass es gegebenenfalls Anwendung findet und benötigt wird und sicherlich kürzer ist als das Schreiben eines Skripts dafür.

user633658
quelle
0

Verwenden Sie den Präprozessor:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

Der Vorteil dieses Ansatzes ist, dass: - es immer noch einfach zu verstehen ist, - es verschiedene Besuche ermöglicht (nicht nur String)

Wenn Sie bereit sind, das erste zu löschen, erstellen Sie sich ein FOREACH () -Makro #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)und schreiben Sie Ihre Besucher in FOREACH (). Dann versuchen Sie eine Codeüberprüfung zu bestehen :).

lorro
quelle