Template-Klassen: class CDerived : public CBase<CDerived::Enum> HowTo?



  • Hallo,

    ich möchte eine Enum-Klasse implementieren, die neben den Enumerationswerten entsprechende String-Werte verwaltet.

    Alter/klassischer Ansatz:

    enum Numbers
    {
      One   = 1,
      Two   = 2,
      Three = 3
    };
    #define ENUM_NUMBER_ONE   TEXT("One")
    #define ENUM_NUMBER_TWO   TEXT("Two")
    #define ENUM_NUMBER_THREE TEXT("Three")
    

    Neuer Ansatz:

    - Eine Enum-Klasse soll die Enumeration und die korrespondierenden String-Werte in sich vereinen.
    - Typsicherheit soll gewährleistet sein.
    - Alle Enumerationsklassen sollen von einer Basisklasse abgeleitet sein, um über Basisklassen-Pointer zugreifen zu können.

    Die String-Werte sollen in der Basis-Template-Klasse CBase verwaltet werden, die Enumeration selbst ist aber in einer abgeleiteten Klasse CDerived deklariert.

    template <class T> class CBase
    {
      ...
    };
    
    class CDerived : public CBase<CDerived::Enum>  // C2065
    {
    public:
      enum Enum
      {
        One   = 1,
        Two   = 2,
        Three = 3
      };
    }
    

    Der aktuelle Code liefert "C2065: 'Enum': undeclared identifier"

    Durch folgende Forward-Deklaration lässt sich der Code compilieren, wobei sich beide Klassendeklarationen gleich verhalten/zu verhalten scheinen:

    enum Enum;
    class CDerived : public CBase<CDerived::Enum>
    class CDerived2 : public CBase<Enum>
    

    Ich verstehe zwar nicht, wieso dies nicht nur compiliert, sondern auch funktioniert, aber was soll's 🙂

    Der Nachteil ist jedoch, dass hierdurch ständig von CDerived::Enum in Enum gecastet werden muss. Die Typsicherheit ist also futsch, da es sich bei Enum um eine leere Enumeration zu handeln scheint.

    Folgende Forward-Deklaration funktioniert NICHT:

    class CDerived;
    enum CDerived::Enum; // C2027
    

    "C2027: use of undefined type 'CDerived'"
    Ok, verstehe ich. Aber mir fällt keine Alternative ein.

    Frage:

    1.) Hat jemand eine Idee, wie sich folgende Deklaration lauffähig machen lässt bzw. ein eindeutiges "geht nicht (,weil...)"?

    class CDerived : public CBase<CDerived::Enum>
    

    2.) Hat jemand eine alternative Lösung zum CBase/CDerived-Ansatz parat?

    Danke & Gruß

    Laerdris



  • #include <iostream>
    using namespace std;
    
    enum Farbe
    {
    	ROT,GRUEN,BLAU,GELB,
    };
    char const* name(Farbe f)
    {
    	static char const* namen[]={"ROT","GRUEN","BLAU","GELB",};
    	return namen[f];
    }
    
    enum Land
    {
    	ENGLAND,DEUTSCHLAND,USA,
    };
    char const* name(Land l)
    {
    	static char const* namen[]={"ENGLAND","DEUTSCHLAND","USA",};
    	return namen[l];
    }
    
    int main()
    {
    	Farbe f=ROT;
    	cout<<name(f)<<'\n';
    	cout<<name(GRUEN)<<'\n';
    }
    


  • Vielleicht reicht ja auch der op <<:

    enum Monat {Jan, Feb, Mar, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez};
    
    std::ostream & operator << (std::ostream & out, Monat monat)
    {
        static char const * namen[] = {
           "Januar", "Februar", "März", "April",
           "Mai", "Juni", "Juli", "August",
           "September", "Oktober", "November", "Dezember"};
    
        out << namen[monat];
        return out;
    }
    
    int main()
    {
      Monat m = Jul;
      std::cout << m << std::endl;
    }
    


  • Hallo,

    heissen Dank für die schnellen Antworten!

    Prinzipiell sind beide vorgeschlagenen Lösungen ausreichend bis perfekt für eine einzelne Enumeration.

    Ich würde aber gerne noch einen Schritt weiter gehen:

    Nehmen wir an, es gäbe neben "Farbe" auch noch "Form". Beide Klassen/Typen/Enumerationen sind in meiner eigentlichen Anwendung voneinander nicht abhängig, also würden 2 separate Klassen genügen.

    Farbe und Form werden aus Text-Configdateien ausgelesen und von String in Enum konvertiert. Auch hierfür ist keine Abhängigkeit (in Form einer Basisklasse) nötig.

    Nun möchte ich aber eine grafische Oberfläche implementieren und dynamisch per Code initialisieren/konfigurieren, und dafür wäre es hilfreich, wenn "Form" und "Farbe" eine gemeinsame Basis hätten und das ganze auch noch Typsicher (per Template-Base) wäre:

    class CEnumFarbe : public CEnumBase<CEnumFarbe::Enum>
    {
      enum Enum
      {
        Red   = 1,
        Green = 2,
        Blue  = 3
      };
      static char const* names[]={"Red","Green","Blue"}; 
    };
    class CEnumForm  : public CEnumBase<CEnumForm ::Enum>
    {
      enum Enum
      {
        Circle = 1,
        Square = 2,
        Triangle = 3
      };
      static char const* names[]={"Circle","Square","Triangle"}; 
    };
    
    BOOL SetChoices(UINT uCtrlID, CEnumBase* pEnumBase) { ... } ;
    
    CEnumFarbe eFarbe;
    CEnumForm  eForm;
    SetChoices(IDC_COMBO_FARBE, eFarbe);
    SetChoices(IDC_COMBO_FORM,  eForm);
    

    Wär doch entzückend, wenn alle meine 'intelligenten' Enum-Klassen ihre jeweiligen Wertebereiche enumerieren könnten und somit meine Controls dynamisch füllen würden.

    Hmm, vielleicht ein bisschen übertrieben parametrisiert, aber irgendwie doch elegant, oder?

    Zumindest wenn's so gehen würde, aber es scheitert ja leider an der Übergabe von "CDerived::Enum" als Template-Parameter für "CBase<class T>" während der Deklaration von "CDerived" (also bevor der Compiler von der Existenz von "CDerived::Enum" weiss)

    Danke und Gruß

    Laerdris



  • #include <iostream>
    using namespace std;
    
    template<typename ENUM>
    struct EnumDescriber;
    
    enum Farbe
    {
    	ROT,GRUEN,BLAU,GELB,
    };
    template<>
    struct EnumDescriber<Farbe>
    {
    	static char const* namen[4];
    };
    char const* EnumDescriber<Farbe>::namen[4]={"ROT","GRUEN","BLAU","GELB",};
    
    enum Land
    {
    	ENGLAND,DEUTSCHLAND,USA,
    };
    template<>
    struct EnumDescriber<Land>
    {
    	static char const* namen[3];
    };
    char const* EnumDescriber<Land>::namen[3]={"ENGLAND","DEUTSCHLAND","USA",};
    
    template<size_t anzahl>
    void fillComboBox(char const* (&namen)[anzahl])
    {
    	for(int i=0;i!=anzahl;++i)
    		cout<<i<<' '<<namen[i]<<'\n';
    }
    
    int main()
    {
    	Farbe f=ROT;
    	cout<<EnumDescriber<Farbe>::namen[f]<<'\n';
    	fillComboBox(EnumDescriber<Land>::namen);
    }
    


  • Hallo Volkard,

    das sieht ja auf den ersten Blick mal sehr interessant aus. Werd ich gleich mal testen und zu verstehen versuchen 🙂

    Gruß

    Laerdris



  • Hallo,

    ich hab den Vorschlag mal zu folgendem Ergebnis eingedampft:

    // .h
    template<typename T> class JPEnum
    { 
    public:
        static char const* namen[]; 
        static T           werte[];
    
        JPEnum(const T& TValue);
        JPEnum(char* lpszValue);
        ~JPEnum();
    
        BOOL  IsValid();
        operator const char*();
        T& operator=(char* lpszValue);
    
    private:
        T m_TValue;
    }; 
    
    enum Farbe 
    { 
        ROT   = 2,
        GRUEN = 3,
        BLAU  = 5,
        GELB  = 7 
    }; 
    char const* JPEnum<Farbe>::namen[]={"ROT","GRUEN","BLAU","GELB",NULL}; 
    Farbe       JPEnum<Farbe>::werte[]={ROT, GRUEN, BLAU, GELB};
    
    enum Land 
    { 
        ENGLAND      = 10,
        DEUTSCHLAND  = 20,
        USA          = 30
    };
    char const* JPEnum<Land> ::namen[]={"ENGLAND","DEUTSCHLAND","USA",NULL}; 
    Land        JPEnum<Land> ::werte[]={ENGLAND,DEUTSCHLAND,USA};
    
    // .cpp
    
    template<typename T> JPEnum<T>::JPEnum(const T& TValue) : m_TValue(TValue) {}
    
    template<typename T> JPEnum<T>::JPEnum(char* lpszValue) : m_TValue((T)-1)
    {
      for(int intIndex = 0 ; namen[intIndex] != NULL ; intIndex++)
      {
        if(!_stricmp(lpszValue, namen[intIndex]))
        {
          this->m_TValue = werte[intIndex];
          return;
        }
      }
    }
    
    template<typename T> JPEnum<T>::~JPEnum() { }
    
    template<typename T> JPEnum<T>::operator const char*()
    {
      for(int intIndex = 0 ; namen[intIndex] != NULL ; intIndex++)
      {
        if(m_TValue == werte[intIndex])
        {
          return namen[intIndex];
        }
      }
      return NULL;
    };
    
    //  ======================================================================
    
    template<typename T> T& JPEnum<T>::operator=(char* lpszValue)
    {
      for(int intIndex = 0 ; namen[intIndex] != NULL ; intIndex++)
      {
        if(!_stricmp(lpszValue, namen[intIndex]))
        {
          this->m_TValue = werte[intIndex];
          return this->m_TValue;
        }
      }
      this->m_TValue = (T)-1;
      return this->m_TValue;
    };
    
    template<typename T> BOOL JPEnum<T>::IsValid()
    {
      return (this->m_TValue != (T)-1);
    }
    
    void Test()
    { 
      // Land
      JPEnum<Land> l = DEUTSCHLAND;
      const char* lpszLand = (const char*) l;
    
      // Farbe
      JPEnum<Farbe> f1 = "BLAU";
      JPEnum<Farbe> f2("Nix");
      f2 = "Gruen";
      f2 = f1;
      const char* lpszFarbe = (const char*) f2;
    }
    

    Das ist zwar nicht exakt das gewünschte Ergebnis, reicht aber aus und hilft mir weiter.

    Optimiert ist ebenfalls noch nicht, die public member müssen noch privatisiert und mit get/set methoden versehen werden.

    Gestolpert bin ich übrigens bei folgender Zeile:

    JPEnum<Farbe> f1 = "BLAU";
    

    Hier hatte ich einen Aufruf von operator= erwartet, bis ich endlich darauf kam dass hier der constructor aufgerufen wird 😛

    Vielen Dank für eure Vorschläge, die Idee mit der Überladung von operator<< fand ich ebenfalls interessant.

    Gruß

    Laerdris


Anmelden zum Antworten