Cast-Operator



  • hallo,

    ich möchte, dass ich meine Template-Klasse CVector2D in verschiedene Typen via eines static_cast casten kann. Also, dass folgendes möglich ist:

    CVector2D<int> IntVec;
    CVector2D<float> FloatVec;
    IntVec=static_cast< CVector2D<int> >(FloatVec);
    

    Wie kann man das machen?



  • ich wuerde einfach einen template zuweisungs operator machen + template ctor



  • aber das würde ja nur für den Fall gehen, wenn ich etwas zuweise.
    Aber wenn ich jetzt eine Vektor-Addition habe, dann ginge das ja nicht mehr:

    cout<< (static_cast< CVector2D<int> >(FloatVec)+IntVec);
    

    Wie funktioniert das? Was muss ich machen, dass der Vektor gecastet wird?



  • Morgen!

    Probier's mal hiermit:
    (der Code ist allgemeingültig gehalten)

    //------------------------------------------------------------------------
    
    #ifndef CLASS1_H
    #define CLASS1_H
    
    #include "iostream.h"
    
    template <class T> class TClass1
    {
       public:
       TClass1(){};
       ~TClass1(){};
    
       void setVal(T v){val=v;}
       void print(){cout<<"value: "<<val;}
    
       //wird aufgerufen, sobald ich signalisiere, dass ich den Typen
       //'TClass1<T2>' verwenden möchte:
       template<class T2> operator TClass1<T2>()
       {
          //ein neues Objekt des geforderten Typs erzeugen:
          TClass1<T2> obj;
          //die Attributwerte des aktuellen Objekts casten und an das neue Objekt
          //vom Typ 'TClass1<T2>' übergeben:
          obj.setVal(static_cast<T2>(val));
          //eine Kopie dieses neuen Objekts zurückgeben:
          return(obj);
       }
    
       protected:
       private:
       T val;
    };
    
    #endif
    
    //------------------------------------------------------------------------
    
    Genutzt werden könnte der oben genannte Operator folgendermaßen:
    
    //'int'-Variante erzeugen und testen:
    TClass1<int> obj;
    obj.setVal(1);
    obj.print();
    
    //Objekt casten und testen:
    ((TClass1<float> )obj).print();
    

    Ich hoffe, dass ich mit dem Vorschlag richtig liege!

    Grüße,
    TS++

    [ Dieser Beitrag wurde am 02.06.2003 um 09:18 Uhr von TS++ editiert. ]

    [ Dieser Beitrag wurde am 02.06.2003 um 09:31 Uhr von TS++ editiert. ]



  • Aber wenn ich jetzt eine Vektor-Addition habe, dann ginge das ja nicht mehr:

    Wie wäre es, wenn du einfach noch ein Template-Operator+ implementierst?

    Dann sparst du dir auch das temporäre Objekt, dass beim casten entstehen würde.



  • Das möchte ich eben nicht machen, damit der Benutzer die Kontrolle hat, welches Objekt gecastet wird. Wenn ich z.B. den +-Operator für einen Typ T und U implementiere, dann stellt sich die Frage, welchen Vektor-Typ ich zurückgeben soll! Typ T oder U? Und das ist verwirrend, weshalb ich diesen Weg nicht gehen werde.



  • dann stellt sich die Frage, welchen Vektor-Typ ich zurückgeben soll! Typ T oder U?

    Meine Antwort wäre: Den passenden.
    Wenn du z.B. in C++ ein int und ein double addierst, dann ist das Ergebnis ein double. Addierst du ein short und ein int, dann erhälst du ein int.

    Genau das würde ich auch bei den Vektoren machen. Hast du einen vector<float> und einen vector<double>, dann ist das Ergebnis der Addition ein vetor<double>.

    Lösen kann man das ganze generisch mit sogenannten Promotion-Traits, da du diesen Weg aber nicht gehen willst, brauche ich das ja auch nicht weiter ausführen 🙂

    Alternativ zum Umwandlungsoperator von TS++ kannst du dir auch eine Funktion der Art:

    template <class Dest, class Source>
    Vector<Dest> vector_cast(const Vector<Source>& v)
    {
        // Unter der Annahme, dass Vector einen Template-Ctor besitzt
        return Vector<Dest>(v);
    }
    

    schreiben.
    Und später:

    int main()
    {
    Vector<int> i(1,2);
    Vector<double> d(3.2,4.3);
    d = d + vector_cast<double>(i);
    }
    


  • Das ist ja unglaublich was es da alles gibt...
    Was sind denn "Promotion-Traits" (es interessiert mich jetzt doch)?



  • Servus Hume!

    Folgendes ist mir nicht ganz klar:

    //'vector_cast' ist doch wirklich eine Funktion, oder!?
    //Ich hab bisher immer gedacht, dass in Templatefunktionen verwendete
    //Datentypen auch immer in '(...)' auftauchen müssen. Hier wird aber
    //'Vector<Source>' bekanntgegeben und intern 'Vector<Dest>' verwendet und
    //auch zurückgegeben. Geht das denn?
    template <class Dest, class Source>
    Vector<Dest> vector_cast(const Vector<Source>& v)
    {
        return Vector<Dest>(v);
    }
    
    //Dann kommst du in 'main' von: 
    'template <class Dest, class Source>
     Vector<Dest> vector_cast(const Vector<Source>& v)'
    //zu: 
    'vector_cast<double>(i)';
    //wie kommst du denn an dieser Stelle zu dem '<double>'?
    //(hab ich *SCHÄM* so noch nie gesehen!)
    //=> sei so gut, und erklär mir das bitte!
    
    int main()
    {
    Vector<int> i(1,2);
    Vector<double> d(3.2,4.3);
    d = d + vector_cast<double>(i);
    }
    

    Danke!

    Grüße,
    TS++

    [ Dieser Beitrag wurde am 02.06.2003 um 15:49 Uhr von TS++ editiert. ]

    [ Dieser Beitrag wurde am 02.06.2003 um 15:49 Uhr von TS++ editiert. ]



  • template<class T, class U> struct promotion_traits {
      typedef T ret;
    }
    
    template<> struct promotion_traits<int, double> {
      typedef double ret;
    }
    
    template<> struct promotion_traits<float, double> {
      typedef double ret;
    } 
    
    template<class T, class U> vector<typename promotion_traits<T, U>::ret> operator+(const vector<T> &, const vector<U> &);
    

    oder so. evtl. is das typename falsch oder unnötig, kA



  • Was sind denn "Promotion-Traits" (es interessiert mich jetzt doch)?

    Weißt du was Traits im Allgemeinen sind? Wenn nicht, empfehle ich den Originalartikel von Nathan Meyers:
    http://tinf2.vub.ac.be/~dvermeir/c++/traits.html

    In einem arithmetischen Ausdruck bezeichnet man die Umwandlung eines kleineren Datentyps in einen größeren als Promotion.

    Promotion-Traits sind also Klassen die zu zwei gegebenen Typen den Typ liefern die ein arithmetischer Ausdruck liefern würde.

    Implementieren tut man sowas entweder mit vielen Spezialisierungen wie Mr.N es gezeigt hat oder über eine Heuristik:

    template <class T, class U>
    struct PromotionTraits
    {
        // Falls sizeof(T) != sizeof(U), wähle den größeren Typ
        // Sonst wähle den floating-Point Typ
        typedef typename Select
        <
            (sizeof(T) > sizeof(U)),
            T,
            typename Select<(sizeof(T) < sizeof(U)), U,
            typename Select<Loki::TypeTraits<T>::isStdFloat, T, U>::Result>::Result
        >::Result Result;    
    };
    

    Select und isStdFloat sind dabei kleine Meta-Template-Helferlein.

    //'vector_cast' ist doch wirklich eine Funktion, oder!?
    //Ich hab bisher immer gedacht, dass in Templatefunktionen verwendete
    //Datentypen auch immer in '(...)' auftauchen müssen. Hier wird aber
    //'Vector<Source>' bekanntgegeben und intern 'Vector<Dest>' verwendet und
    //auch zurückgegeben. Geht das denn?

    Ja. Das geht. Allerdings gibt es dabei ein paar Einschränkung.
    Der Compiler kann beim Aufruf einer Template-Funktion immer nur die Templateparameter automatisch herleiten, die in der Parameterliste der Funktion auftauchen (hier Source). Alle anderen müssen vom Aufrufer explizit angegeben werden.

    //wie kommst du denn an dieser Stelle zu dem '<double>'?
    //(hab ich *SCHÄM* so noch nie gesehen!)
    //=> sei so gut, und erklär mir das bitte!

    Das ist genau eine solche explizite Angabe eines Template-Parameters. Source wird durch den Compiler automatisch hergeleitet (im Beispiel nach int). Dest wird explizit angegeben (hier double).

    Die explizite Angabe erfolgt dabei immer von links nach rechts. Wenn ich also schreibe:

    vector_cast<foo, bar>(einObjekt);
    

    dann ist Dest = foo und Source = bar.

    evtl. is das typename falsch oder unnötig

    Es ist richtig und nötig 🙂



  • OK, Danke Hume!

    Also wenn ich das richtig verstanden habe, dann gilt für Templatefunktionen
    allgemein folgende Struktur:

    template<class T1,...class Tn> <T...> funcName(T1 param1, ... Tn paramn)
    
    Wobei ich, wie bekannt, bei folgendem Beispiel
    
    template<class T1, class T2> T1 func(T1 param1, T2 param2)
    {
       . . . 
    }
    
    durch 'func(0.12f,'a')' eine Herleitung von 'T1' und 'T2' durch den Compiler
    ermöglichen kann. Und du meinst also, ich könnte den gleichen Effekt auch durch explizite Typenangabe erreichen?
    In etwa so:   func<float,char> (); ?
    
    Ich kenn diese explizite Angabe jetzt nur von Templateklassen!
    Ist diese Vorgehensweise auch bei Templatemethoden möglich?
    Ach und übrigens: Ich hab über diese explizite Typenangabe bei Templatefunktionen nichts in den Vorlesungen gehört. Wo hast du denn die Details her?
    
    Grüße,
    TS++
    


  • Und du meinst also, ich könnte den gleichen Effekt auch durch explizite Typenangabe erreichen?
    In etwa so: func<float,char> (); ?

    Nicht ganz. Deine Funktion func erwartet zwei Parameter. Du musst also schon zwei Argumente übergeben. Du kannst nur die *Typen* für T1 und T2 explizit angeben:

    template <class T1, class T2>
    void Func(T1 a, T2 b)
    {}
    
    int main()
    {
        int a;
        char b;
        // implizite Herleitung von T1 und T2 durch Compiler
        // T1 = int, T2 = char
        Func(a, b); 
    
        // explizite Angabe von T1 = int. Implizite Herleitung von T2 = char
        Func<int>(a, b);
    
        // explizite Angabe von T1 = int und T2 = char
        Func<int, char>(a, b);
    
        // explizite Angabe von T1 = int, T2 = double.
        // Da char (der Typ von b) nach double konvertiert werden kann ist der
        // Aufruf ok
        Func<int, double>(a,b);
    
        // Error: T1 ist int und T2 ist char*. Es existiert aber keine
        // Konvertierung von char (der Typ von b) nach char* (der Typ von T2).
        Func<int, char*>(a, b);
    }
    

    Ich kenn diese explizite Angabe jetzt nur von Templateklassen!

    Ist letztlich das selbe Prinzip. Bei Klassen *muss* man bei der Instanziierung die Templateparameter explizit angeben. Bei Funktionen ist dies optional, da Templateparameter die in der Parameterliste der Funktion auftauchen automatisch auf Basis der aktuellen Parameter hergeleitet werden.

    Ist diese Vorgehensweise auch bei Templatemethoden möglich?

    Yep.

    Wo hast du denn die Details her?

    Neben dem C++ Standard ist C++ Templates - The complete guide von Josuttis und Vandevoorde *die* Quelle für alle Antworten rund um Templates. Das Buch ist der Hammer, allerdings nichts für Leute, die nur mal kurz reinschnuppern wollen.

    Das hier besprochene und harmlose Detail von Funktionstemplates findest du aber eigentlich in jedem ordentlichen C++ Buch erklärt (z.B. in Stroustrups "The C++ Programming Language" oder im "C++ Primer" von Stanley B. Lippman).



  • Ok, danke Hume!
    Wieder mal schlauer geworden!
    (wie soll das nur weitergehen;))

    Grüße,
    TS++

    [ Dieser Beitrag wurde am 03.06.2003 um 08:23 Uhr von TS++ editiert. ]


Anmelden zum Antworten