enum zu string
-
Verdirb die Jugend doch nicht schon so früh!
-
Fellhuhn schrieb:
Da ist es wohl am einfachsten die gewünschen Enums in einer extra Datei zu verwalten und sich von einem Skript zusammenschustern zu lassen.
Naja, je nachdem ist eine Implementierung in C++ gar nicht so kompliziert. Sobald man zum Beispiel auf Compilezeit-Sicherheit verzichtet, hat man bereits sehr viele Möglichkeiten. Mit dem dynamischen Skript hätte man diesen Nachteil ohnehin auch.
-
Nexus schrieb:
Vielleicht sollten wir uns darüber unterhalten, welche genauen Anforderungen an die Enumeration gestellt werden. Die fett geschriebenen wurden bisher genannt, ich habe ein paar weitere mögliche aufgelistet:
- Keine Codeduplizierung bei der Definition
- Konvertierbarkeit zu Strings
- Implizite/Explizite Konvertierbarkeit (von und) zu Ganzzahlen
- Compilezeitfehler bei Zugriff auf nicht existenten Enumerator
- Iteration durch alle Enumeratoren
- ...
Je mehr Kriterien erfüllt werden müssen, desto mühsamer wird natürlich eine Implementierung. Praktisch wäre auch ein vollständiges (und dennoch möglichst kleines) Beispiel, welches zeigt, was das
enum
alles leisten soll.Ich hab jetzt mal nochwas gebastelt, was alle Kriterien erfüllt, außer Konvertierung von einer Ganzzahl (geht nur, indem man über alle Werte iteriert) und außerdem ermöglicht, dass man nachträglich neue Werte hinzufügt (ich könnte mir vorstellen, dass das manchmal ganz nützlich ist). Nachteil ist, dass der Name als
std::string
bei einer Parameterübergabe kopiert wird, das könnte man durchconst char*
vermeiden, man hat dann aber nicht mehr diestring
-Komfortabilität.Lauffähiges Beispiel:
#include <limits> #include <string> #include <vector> #include <iostream> #include <boost/scoped_ptr.hpp> template<typename T> class enum_entry { public: typedef std::vector<enum_entry<T>*> container; enum_entry() {} enum_entry(std::string name, int value = start) : name(name), value(value) { start = value+1; if(all_entries == NULL) // Ehe jemand fragt: wenn ich all_entries nicht als Zeiger gemacht hab, kam es zu Abstürzen, offenbar wurde die statische Variable nicht vor dem Konstruktor initialisiert all_entries.reset(new container); all_entries->push_back(this); } std::string name; int value; static const container& all() { if(all_entries == NULL) all_entries.reset(new container); return *all_entries; } private: static int start; static boost::scoped_ptr<container> all_entries; }; template<typename T> int enum_entry<T>::start = 0; template<typename T> boost::scoped_ptr<typename enum_entry<T>::container> enum_entry<T>::all_entries(NULL); #define NAMED_ENUM(name) class name##_enum_impl {}; typedef enum_entry<name##_enum_impl> name; const name #define ENTRY(e) e(#e) #define ENTRY_NAME(e, name) e(name) #define ENTRY_VALUE(e, value) e(#e, value) #define ENTRY_NAME_VALUE(e, name, value) e(name, value) #define NEW_ENTRY(typ, e) const typ e(#e); #define NEW_ENTRY_NAME(typ, e, name) const typ e(name); #define NEW_ENTRY_VALUE(typ, e, value) const typ e(#e, value); #define NEW_ENTRY_NAME_VALUE(typ, e, name, value) const typ e(name, value); // Beispiel-Enums NAMED_ENUM(farbe) ENTRY(rot), ENTRY(blau), ENTRY_NAME(gruen, "grün"); NAMED_ENUM(status) ENTRY(OK), ENTRY_VALUE(ERROR, -1), ENTRY_NAME_VALUE(CRITICAL, "critical error", -2), ENTRY(GOOD); // Später neuen Eintrag hinzufügen NEW_ENTRY(farbe, gelb) int main() { using namespace std; farbe f1 = rot, f2 = blau, f3 = gruen, f4 = gelb; cout << f1.value << " " << f2.value << " " << f3.value << " " << f4.value << endl; cout << f1.name << " " << f2.name << " " << f3.name << " " << f4.name << endl; status s1 = OK, s2 = ERROR, s3 = CRITICAL, s4 = GOOD; cout << s1.value << " " << s2.value << " " << s3.value << " " << s4.value << endl; cout << s1.name << " " << s2.name << " " << s3.name << " " << s4.value << endl; for(farbe::container::const_iterator i = farbe::all().begin(); i != farbe::all().end(); ++i) cout << (*i)->value << ": " << (*i)->name << endl; }
Ausgabe:
0 1 2 3 rot blau grün gelb 0 -1 -2 -1 OK ERROR critical error GOOD 0: rot 1: blau 2: grün 3: gelb
Kritik ist natürlich immer gern gesehen
Ach ja, noch ein Hinweis: da sich die
enum_entry
s nicht von selbst ausall_entries
austragen, sollte man sie nur als globale (oder lokale statische) Variablen anlegen. Natürlich könnte man das noch hinzufügen, allerdings ist es nicht wirklich Sinn von Enums, dass man z.B. in Funktionen neue, temporäre Werte zaubert.
-
Sieht wirklich nett aus. Ich hatte was Ähnliches im Sinn, auch mit der Typgenerierung über Templates, allerdings waren meine Ansätze teilweise etwas weniger elegant.
std::vector<enum_entry<T>* const>
Geht das? Eigentlich müssen Elemente in STL-Containern kopierkonstruierbar und zuweisbar sein, was auf
const
-qualifizierte Objekte nicht zutrifft.Warum eigentlich
__LINE__
?Die Linkage dürfte zudem Schwierigkeiten bereiten, sobald man z.B. eine Enumeration im Header anlegt. Ich würde die Enumeratoren mit
const
versehen, dann hast du interne Linkage.Ein "Problem" ist auch noch, dass der Benutzer nachträglich Objekte hinzufügen kann. Dies kann erwünscht sein, allerdings wird die Enumeration sehr schnell unübersichtlich, wenn die Enumeratoren aus verschiedenen Stellen im Code zusammengetragen werden. Besonders wenn Header und Implementierungsdatei getrennt werden. Man könnte die entsprechenden Makros weglassen, allerdings wäre ein nachträgliches Hinzufügen prinzipiell immer noch möglich. Da hilft wohl nur die totale Obfuscation, sodass sich niemand mehr getraut, statt der Makros den direkten Code hinzuschreiben.
-
Übrigens war meine Kriterienliste eher so gedacht, dass unskilled sich ein paar Punkte davon auswählt, damit wir ihm eine Lösung zurechtstricken können. Sonst sind die Vorschläge viel komplizierter als eigentlich nötig.
-
Erstmal vielen Dank für das Feedback.
Nexus schrieb:
std::vector<enum_entry<T>* const>
Geht das? Eigentlich müssen Elemente in STL-Containern kopierkonstruierbar und zuweisbar sein, was auf
const
-qualifizierte Objekte nicht zutrifft.Hm, ich hatte mich verschrieben. Ich wollte Zeiger auf konstante Objekte. Ich werdes mal rausmachen. (Bei mir gabs mit dem const übrigens keine Probleme, vielleicht ist VS hier tolerant.)
Nexus schrieb:
Warum eigentlich
__LINE__
?Zu jedem Enum-Typ brauch man ein eigenes T für enum_entry, damit start und all_entries jeweils unterschiedlich ist. Also brauche ich jeweils eine eindeutige neue Klasse und dafür hab ich eben name##__LINE__ genommen (name geht nicht, da dann das typedef nicht mehr funktionieren würde). Ich hätte auch statt dem typedef eine Klasse name nehmen können, die von enum_entry abgeleitet ist, aber dann hätte ich Konstruktoren und typedefs nochmal schreiben müssen und dazu war ich zu faul
Nexus schrieb:
Die Linkage dürfte zudem Schwierigkeiten bereiten, sobald man z.B. eine Enumeration im Header anlegt. Ich würde die Enumeratoren mit
const
versehen, dann hast du interne Linkage.Gute Idee. Ich hatte zwar auch Konstrukte wie
gelb.name = "neues Gelb"
im Sinn, aber man kann ja nicht alles haben. Ich ändere es einfach mal.
-
ipsec schrieb:
Zu jedem Enum-Typ brauch man ein eigenes T für enum_entry, damit start und all_entries jeweils unterschiedlich ist. Also brauche ich jeweils eine eindeutige neue Klasse und dafür hab ich eben name##__LINE__ genommen (name geht nicht, da dann das typedef nicht mehr funktionieren würde).
Würde nicht auch
name##ImplementationDefinedEnum
funktionieren (mit sinnvollem Bezeichner)? Wäre vielleicht etwas klarer, sollte einmal Präprozessordebugging nötig werden. Ausserdem werden dann mehrere Definitionen mit gleichem Namen sofort erkannt und vom Compiler verboten.ipsec schrieb:
Gute Idee. Ich hatte zwar auch Konstrukte wie
gelb.name = "neues Gelb"
im Sinn, aber man kann ja nicht alles haben. Ich ändere es einfach mal.Du kannst ja auch
static
oder anonyme Namensräume nehmen, aber dann hast du pro Übersetzungseinheit eine andere Enumeration, was für böse Überraschungen sorgen könnte.extern
geht nicht ohne Codeduplizierung, da es eine Definition in einer .cpp-Datei benötigt.
-
Nexus schrieb:
ipsec schrieb:
Zu jedem Enum-Typ brauch man ein eigenes T für enum_entry, damit start und all_entries jeweils unterschiedlich ist. Also brauche ich jeweils eine eindeutige neue Klasse und dafür hab ich eben name##__LINE__ genommen (name geht nicht, da dann das typedef nicht mehr funktionieren würde).
Würde nicht auch
name##ImplementationDefinedEnum
funktionieren (mit sinnvollem Bezeichner)? Wäre vielleicht etwas klarer, sollte einmal Präprozessordebugging nötig werden. Ausserdem werden dann mehrere Definitionen mit gleichem Namen sofort erkannt und vom Compiler verboten.Ja, sowas geht auch. Ich hatte jetzt gar nicht beachtet, dass auch name immer unterschiedlich ist.