VC++ workaround: template<class T> class C<T&>{};



  • sorry, ich weiss nicht wie dieses C++ feature heisst.

    ich will ein template fuer referenzen spezialisieren:

    template<class T>
    struct isReference
    {
      enum { result = 0 };
    };
    
    template<class T>
    struct isReference<T&>
    {
      enum { result = 1 };
    };
    

    das ist ja standardkonform.
    doch der VC++ (7 in diesem fall, aber ich nehme an, dass der 6er es genausowenig kann) meldet nen syntax error -> ergo: dieses feature wird nicht unterstuetzt.

    doch das kann man sicher irgendwie simulieren - boost und loki koennen das ja auch -> nur ich steig durch den code leider nicht durch 😞



  • sorry, ich weiss nicht wie dieses C++ feature heisst

    Partielle Template Spezialisierung.

    ich will ein template fuer referenzen spezialisieren

    Die gute Nachricht: Wenn du nur rausfinden willst (ja/nein), ob es sich bei T um eine Referenz handelt, dann kann dir geholfen werden. Wenn du allerdings von T& auf T willst, dann gibt es keinen Workaround. Das erfordert zwingend partielle Spezialisierung.

    Nun zu den Workarounds.
    Der für den VC 7 ist einfach:

    // Macht aus einem Typ einen anderen Typ
    // Vorteil: Keinerlei Einschränkungen wie z.B. "T hat Default-Ctor"
    // nötig.
    template <typename T>
    struct Type2Type
    {   
        Type2Type(){} // VC7
    };
    
    // es gilt garantiert: sizeof(yes) != sizeof(no)
    typedef char (&yes)[1];
    typedef char (&no) [2];
    
    template <class T>
    struct isReference
    {
    private:
        // Kann nur für Referenzen verwendet werden, da Type2Type keinerlei
        // Konvertierungen zulässt.
        template<typename U>
        static yes is_reference(Type2Type<U&>);
        // Die drei Punkt matchen von allen möglichen Konvertierungen am 
        // schlechtesten. Diese Funktion ist also der Notanker.
        static no  is_reference(...);
    public:
        // sizeof-wonderland stößt die Überladungsauflösung an.
        enum {result = sizeof(is_reference(Type2Type<T>())) == sizeof(yes)};
    };
    

    Mit dem VC 6 wird's brutal komplizierter:

    typedef char (&yes)[1];
    typedef char (&no) [2];
    
    template <class T>
    struct isReference
    {
    private:
    	// Ok. Ganz langsam. 
    	// isReferenceHelper1 ist eine Funktion die ein Type2Type<U> als
    	// Parameter erwartet und einen *Funktionszeiger* vom Typ
    	// U& (*)(Type2Type<U>) liefert.
    	// Man beachte den Rückgabewert der neuen Funktion.
    	// Dieser ist eine *Referenz*
    	template <class U>
    	static U&(* isReferenceHelper1(Type2Type<U>) )(Type2Type<U>);
    	static yes	isReferenceHelper1(...);
    
    	// Und weiter:
    	// isReferenceHelper2 ist eine Funktion, die einen *Funktionszeiger*
    	// vom Typ U& (*)(Type2Type<U>).
    	// Also genau so ein Funktionszeiger, wie er von isReferenceHelper1 geliefert
    	// wird.
    	//
    	// *Der Trick* beruht auf SFINAE = Substitution Failure is not an error
    	// D.h. führt die Auflösung von Templateparametern zu einem ungültigen
    	// *Typ*, dann wird kein Compilerfehler hervorgerufen,
    	// sondern einfach die Templatefunktion aus der Menge der möglichen 
    	// Funktionen entfernt.
    	// 
    	// Falls *T* eine Referenz (T&) ist, dann wird der Template Parameter 
    	// U von isReference1 zu T&. Dadurch wird der Typ des Funktionszeigers aber:
    	// T&& (*)(Type2Type<T&>)
    	// Die Funktion liefert also eine Referenz-auf-Referenz und das ist ein
    	// ungültiger *Typ*. 
    	// Die Template-Überladung für T = T& fällt also weg. 
             // Es bleibt die Version mit der Ellipse. Diese ist aufrufbar und liefert yes.
             // Bingo! Warum jetzt aber dann noch isReferenceHelper2?
    	// Kurz: Bug im VC 6.
    	// Eigentlich sollte isRefrenceHelper1 reichen. Der VC 6 wählt aber dummerweise
    	// immer die Template-Funktion. 
    	// Durch die zusätzliche Indirektion (ein Mittel, dass in der Informatik
    	// immer hilft), bekommt er es aber dann doch noch hin.
    	template <class U>
    	static no	isReferenceHelper2(U&(*)(Type2Type<U>));
    	static yes isReferenceHelper2(...);
    public:
    	enum {
    		result = sizeof(isReferenceHelper2(isReferenceHelper1(Type2Type<T>())))
    				== sizeof(yes)
    	};
    };
    

    Der aufmerksame Leser wird feststellen, dass dies genau die Workaoround aus Loki sind. Andere kenne ich aber nicht und vielleicht helfen ja die Kommentare.

    Auf einen Blick:
    Template-Workarounds basieren sehr häufig auf zwei Techniken. A) Überladung und 😎 SFINAE.
    Hilft beides nicht, hilft meist "another level of indirection"



  • liegt es an mir oder an diesem workaround dass mir schwindlich ist?

    das lustigste daran ist ja: es funktioniert wirklich 🙂

    danke vielmals.
    den Loki code habe ich mir angesehen - aber jetzt ist mir klar, warum ich das ohne deiner erklaerung nicht kapiert habe...

    nur eine frage noch: was macht Type2Type?
    sehe ich das richtig, dass der VC7 workaround nur eine vereinfachte form des VC6 workarounds ist?
    wenn ja, dann ist Type2Type klar 🙂



  • sehe ich das richtig, dass der VC7 workaround nur eine vereinfachte form des VC6 workarounds ist?

    Richtig. Der VC 7 workaround basiert nur auf Überladungsauflösung. Beim VC 6 muss man etwas mehr arbeiten, da er bei dem VC 7 Code mit einem ICE stirbt.

    Vielleicht wird das Type2Type klar, wenn man es erstmal weglässt.
    Um mit Überladung zu testen ob T eine Referenz ist, brauchst du eine Funktion die eine Referenz erwartet, sowie eine die mit allem anderen umgehen kann.

    template<typename U> 
    yes is_reference(U&); 
    no  is_reference(...);
    

    Um den Test durchzuführen brauchst du jetzt natürlich noch ein Argument. Sprich ein T.
    Nur woher nehmen, wenn nicht stehlen.
    Der simpelste Versuch geht in die Hose:

    template <class T> 
    struct isReference 
    { 
    public: 
        enum {result = sizeof(is_reference(T())) == sizeof(yes)}; 
    };
    

    Das geht nicht, wenn T eine Refrenz ist, da T&() kein gültiger Ausdruck ist.
    Ok. Aber noch sind wir nicht am Ende. Wenn wir das T-Objekt nicht direkt erzeugen können, dann vielleicht indirekt:

    template <class T> 
    struct isReference 
    { 
    private:
    static T MakeT();
    public: 
        enum {result = sizeof(is_reference(MakeT())) == sizeof(yes)}; 
    };
    

    Das geht. Alles läuft toll. Bis irgendjemand folgendes versucht:

    isReference<int [5]>::result;
    

    Woops. MakeT liefert nun aufeinmal ein int-Array und das ist nicht erlaubt.

    Das Type2Type-Typs ist also nur ein Trick um für einen beliebigen Typ T einen gültigen Typ erzeugen zu können, den wir dann zum Testen verwenden können. Wir haben keine Ahnung was T sein kann. Also suchen wir uns etwas über das wir besser einschätzen können.

    <edit>Fehler in der zweiten Version von isReference verbessert.</edit>



  • HumeSikkins schrieb:

    Das geht nicht, wenn T eine Refrenz ist, da T&() kein gültiger Ausdruck ist.

    💡
    manchmal sieht man die typen vor lauter templates nicht

    danke!
    jetzt ist alles klar.


Anmelden zum Antworten