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 durch const char* vermeiden, man hat dann aber nicht mehr die string -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 aus all_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.


Anmelden zum Antworten