Template-Klasse - Template-Parameter nur von bestimmter Klasse erlauben?



  • mit typetraits könnte (noch nicht gemacht da ich nur fundamentale typen teste) es gehen wenn dir alle ableitungen schon zur compiletime klar sind, wenn allerdings der user von deiner basisklasse erben darf dann schaffst du es natürlich nicht.



  • Folgende grauslig aussehende Lösung funktioniert (nach Modern C++ Design von Andrei Alexandrei Alexandrescu):

    template <class T, class U>
    class Conversion
    {
    
      typedef char Small;
      class Big { char dummy[2]; };
      static Small Test(U);
      static Big Test(...);
      static T MakeT();
     public:
      enum { exists = sizeof(Test(MakeT())) == sizeof(Small) };
      enum { sameType = false };
    };
    
    template <class T>
    class Conversion<T, T>
    {
    public:
      enum { exists = 1, sameType = 1 };
    };
    
    #define SUPERSUBCLASS(T, U) \
    (Conversion<const U*, const T*>::exists && \
    !Conversion<const T*, const void*>::sameType)
    
    #define SUPERSUBCLASS_STRICT(T, U) \
    (SUPERSUBCLASS(T, U) && \
    !Conversion<const T, const U>::sameType)
    
    #define STATIC_CHECK(expr) { char unnamed[(expr) ? 1 : 0]; }
    
    class Base {
    };
    
    class Derived : public Base {
    };
    
    class Other {};
    
    template<class T>
    class tollesTemplate {
    	public:
    	  tollesTemplate() {
          	STATIC_CHECK(SUPERSUBCLASS(Base,T));
    	  }
    };
    int main ()
    {
    	tollesTemplate<Base> a;
    	tollesTemplate<Derived> b;
    	//tollesTemplate<Other> c; compiliert nicht weil nicht von Base abgeleitet
    
    }
    

    Nutzt man jetzt das Template mit einer anderen Klasse als Base oder davon abgeleitet gibt es einen Compilerfehler (der einem so wie hier gemacht nichts sinnvolles sagt; das kann man allerdings auch verbessern).
    Lustig, aber ich wäre nicht so begeistert, wenn das in Produktivcode auftaucht, den ggf. auch noch andere Leute pflegen müssen.
    Ausserdem scheint es nicht jeder Compiler zu mögen. Eine etwas ältere gcc Version habe ich nicht dazu überredet bekommen, VC7.1 schon.

    Warum funktioniert Dein Template eigentlich nicht, wenn jemand eine Klasse reinsteckt, die nicht von Deiner Basisklasse abgeleitet ist, aber dasselbe (für das Template notwendige) Interface bietet? Ist es wirklich notwendig dafür das Kompilieren zu unterbinden?



  • niemand schrieb:

    Folgende grauslig aussehende Lösung funktioniert (nach Modern C++ Design von Andrei Alexandrei Alexandrescu):

    ich hab doch gewusst das etwas drin steht habe es aber nicht gefunden 😡 und blattere schon ne halbe stunde wie blöd drin rum.



  • In meiner Ausgabe Kapitel 2.7 "Detecting Convertibility and Inheritance at Compile Time"



  • Hallo niemand:
    Uhoh, das sieht echt grauslig aus.
    Im Grunde habt ihr recht: Wenn die Klasse das entsprechende Interface bietet... Wieso nicht.



  • niemand schrieb:

    template <class T, class U>
    class Conversion
    {
    
      typedef char Small;
      class Big { char dummy[2]; };
      static Small Test(U);
      static Big Test(...);
      static T MakeT();
     public:
      enum { exists = sizeof(Test(MakeT())) == sizeof(Small) };
      enum { sameType = false };
    };
    
    template <class T>
    class Conversion<T, T>
    {
    public:
      enum { exists = 1, sameType = 1 };
    };
    
    #define SUPERSUBCLASS(T, U) \
    (Conversion<const U*, const T*>::exists && \
    !Conversion<const T*, const void*>::sameType)
    
    #define SUPERSUBCLASS_STRICT(T, U) \
    (SUPERSUBCLASS(T, U) && \
    !Conversion<const T, const U>::sameType)
    
    #define STATIC_CHECK(expr) { char unnamed[(expr) ? 1 : 0]; }
    
    class Base {
    };
    
    class Derived : public Base {
    };
    
    class Other {};
    
    template<class T>
    class tollesTemplate {
    	public:
    	  tollesTemplate() {
          	STATIC_CHECK(SUPERSUBCLASS(Base,T));
    	  }
    };
    int main ()
    {
    	tollesTemplate<Base> a;
    	tollesTemplate<Derived> b;
    	//tollesTemplate<Other> c; compiliert nicht weil nicht von Base abgeleitet
       
    }
    

    statt

    STATIC_CHECK(SUPERSUBCLASS(Base,T));
    

    kannste auch

    (void)(Base*)(T*)0;
    

    scheiben, tut genau das geiche machen, ist aber ohne meta-programmierung. ok, tut nicht gegen Base==void schützen, aber wer macht das schon?



  • 🙂
    Mal wieder zu kompliziert gedacht...

    Unterschied zu dem Makro SUPERSUBCLASS ist allerdings, dass das Makro einen zu Compilezeit auswertbaren Ausdruck ergibt (also nicht einen Compilefehler). Der Compilefehler wird ja erst durch das STATIC_CHECK erzwungen und den hätte man in der Tat mit dem einfachen Cast auch hinbekommen und es ist viel einfacher.
    Sorry...



  • volkard schrieb:

    (void)(Base*)(T*)0;
    

    Tut bei mir genau garnichts tuten tun. Ist mir auch nicht klar, warum das tuten sollte.

    Base* b = (T*)0;
    

    Hat den gewünschten Effekt bei mir.

    Mir ist auch nicht klar, wie ich deinen Ausdruck parsen sollte, volkard, da mir die Regeln hier etwas zu fies werden. Man könnte das vielleicht als Funktionsdeklaration, vielleicht aber auch als eine Anreihnung von C-Casts sehen, wobei mir weder noch so richtig sinnvoll erscheint (darf man nach void casten?). Selbst wenn es casts wären, würde man genau die Konverierbarkeit, die man ja testen will, durch die C-Casts in die Tonne treten tuten tun.



  • 7H3 N4C3R schrieb:

    darf man nach void casten?

    ja. hab ich neulich im standard gelesen. macht die warnung "berechneter wert wird nicht benutz" oder "code hat keinen effekt" weg.

    Selbst wenn es casts wären, würde man genau die Konverierbarkeit, die man ja testen will, durch die C-Casts in die Tonne treten tuten tun.

    das ist dann ein fehler von mir und meinem compiler. fehlert's bei dir mit static_cast, also (void)static_cast<Base*>((T*)0) ?



  • 7H3 N4C3R schrieb:

    Base* b = (T*)0;
    

    Hat den gewünschten Effekt bei mir.

    //alles folgende ungetestet, nur aus erinnerung
    macht aber warnung "b wird nicht benutzt". müßte man nich wegmachen, indem man b benutzt.

    Base* b = (T*)0;
    b;
    

    "ups, code has no effect" sagt er nu.

    Base* b = (T*)0;
    (void)b;
    

    hihi, klappt.



  • Japps, static_cast fehlert auch bei mir 🙂 static_cast initialisiert ja eben genau ein neues Objekt mit dem Wert und interpretiert nicht nur einfach neu, wie es ein C-Cast machen würde.



  • Ich kann trotzdem nicht nachvollziehen warum man ein Template schreibt um anschliessend mit irgendwelchen schrägen Tricks sicherzustellen dass das Template nur mit einem Typ(und seine Ableitungen) instanziert werden kann. 😕
    Irgendwie wird es zur Mode jeden Scheiss in nen Template zu packen,ob es nun Sinn macht oder auch nicht.

    MfG Spacelord



  • Spacelord schrieb:

    Ich kann trotzdem nicht nachvollziehen warum man ein Template schreibt um anschliessend mit irgendwelchen schrägen Tricks sicherzustellen dass das Template nur mit einem Typ(und seine Ableitungen) instanziert werden kann.

    templates haben null overhead. außerdem ist es gar nicht möglich bei manchen sachen, das durch vererbung zu lösen. so haste zum beispiel die sizeof-information verloren, wenn du nen basisklassenzeiger übergibst statt des genauen treffenden zeigers. und wenn deine funktion beides braucht, sizeof-info UND will was virtuelles der basisklasse aufrufen, denkt man gleich an so nen check.
    meistens (99,9%?) reicht es vollauf, wenn die klasse die gleiche schnittstelle hat. aber manchmal kann es dumm kommen, und man macht was ganz ungewöhnliches. ne unnormale schnittstelle. ein ".c_str()", was einen auto_ptr zurückgibt. ein konstruktor, der nix tut und noch init() verlangt ist. komische sachen halt. dann sagt man sich, wenn man die komischen sachen in seinem template benutzt: "uih, ich rufe das komische-sachen-benutzende template besser wirklich nur mit komischen klassen auf".

    Irgendwie wird es zur Mode jeden Scheiss in nen Template zu packen,ob es nun Sinn macht oder auch nicht.
    

    es übt. die gemeinde erforscht erstmal, was man alles mit templates machen kann. und das ist unglaublich viel mehr, als man ihnen ansieht. ist ein wenig, wie wenn man in assembler ein betriebssystem schreibt. und wir einzelnen wenden uns nicht ab, sondern sehen mit glänzenden augen, was die gurus machen und wollen im kleinen auch sowas machen. ist auch gut so. nur aus ner breiten basis entstehen bedeutende gurus in ausreichender menge, daß wir irgendwann mal beginnen können, dran zu glauben, daß wir in ansätzen c++ kapieren.
    und es wird sich lohnen. wir erforschen ja nicht nur c++, sondern wir erforschen eigentlich, wie die c++-nachfolger aussehen könnten. was hat sich gelohnt, was war ein irrweg, was geht und was geht nicht?


Anmelden zum Antworten