templates



  • hi, lese gerade ein tutorial durch indem folgendes vorkommt:

    template <class T, class U>
    T GetMin (T a, U b) {
      return (a<b?a:b);
    }
    

    Wenn ich nun Die funktion getmin mit 2 verschiedenen typen T und U aufrufe kann sie doch nur einen typen T returnen? also wenn b returnt werden soll sollte es eigentl. nicht mögl sein (mit korrektem typ)?



  • Halozination schrieb:

    Wenn ich nun Die funktion getmin mit 2 verschiedenen typen T und U aufrufe kann sie doch nur einen typen T returnen? also wenn b returnt werden soll sollte es eigentl. nicht mögl sein (mit korrektem typ)?

    Das geht genau dann, wenn die Funktion handgeschrieben mit den beiden Typen auch funtktionieren würde. Dazu muss zum Einen ein U in ein T konvertiert werden können (damit ggf. b zurückgegeben werden kann), zum Anderen muss der Ausdruck a<b ausgewertet werden, d.h. es muss ein operator<(T,U) oder, da die Konvertierung ja auch exisiteren muss, ein operator<(T,T) existieren. Wobei egal ist, ob der operator als freie Funktion existiert, als Member von T oder ob irgendein op< existiert, für den T und U in die beiden Argumente konvertierbar sind. Beispiele sind double und int, da sind die Konvertierungen und vergleiche möglich.



  • Etwas "verrückte" Anwendung dessen, was ich grade geschrieben hab:

    #include <iostream>
    
    struct A {
      int i;
    };
    struct B {
      int i;
      B(A const& a) : i(a.i) {}
      B(int i) : i(i) {}
    };
    struct C {
     int i;
     C(B const& b) : i(b.i) {}
    };
    
    bool operator<(C const& l, B const& r)
    {
      return l.i < r.i;
    }
    
    template <class T, class U>
    T GetMin (T a, U b) {
      return (a<b?a:b);
    }
    
    int main()
    {
      A myA; myA.i = 10;
      B myB = 12;
    
      C myC = GetMin(myB, myA); //!!!
    
      std::cout << myC.i << '\n';
    }
    

    Hier wird GetMin<B, A> aufgerufen
    der Vergleich ist dann myB<myA, also ein Vergleich von B und A.
    Der Compiler findet keinen op<, der ein B und ein A als Operanden nimmt. Er versucht also zuerst, einen op< zu finden, bei dem einer der beiden Operanden konvertiert wird, also op<(B, ?) oder op<(?, A), wobei der zweite/erste Operand in den Typ ? konvertiert werden kann. Gibts auch beides nicht.
    Also versucht er was zu finden, wo er beides konvertiert. Das gibts auch:
    op< (C, B), so dass er myB in ein C konvertiert und myA in ein B.
    Er wertet den Operator also aus, in dem Fall kommt false raus. Also muss myA zurückgegeben werden und an myC gegeben werden. Auf den ersten Blick geht das nicht, aber der Rückgabetyp des Templates ist ja T, in diesem Fall also B. Es wird also erst im return-statement myA in ein B konvertiert, danach im Konstruktor von myC das B in ein C konvertiert.



  • Wer macht denn solche Tutorials? Keine Referenzen werden genutzt ➡ Worst Case wird da kopiert!



  • ty pumuckl. da ich nur zahlendatentypen in das funktions template übergeben würde, würde wohl ein vergleichsoperator existieren, trotzdem ganz interessant zu sehen wie du es mit den stuct vergleichen gemacht hast.

    problem ist dass ich nicht einfach konvertieren kann und das ganze wohl doch etwas komplizierter wird weil ich möglicherweise präzise => unpräzise konvertiere und daten verloren gehen. (T unpräzise, U präzise).

    habe dein beispiel aber noch nicht ganz verstanden, was passiert in den folgenden zeilen am besten befehl für befehl.

    struct B {
      int i;
      B(A const& a) : i(a.i) {}
      B(int i) : i(i) {}
    };
    

    EOutOfResources schrieb:

    Wer macht denn solche Tutorials? Keine Referenzen werden genutzt ➡ Worst Case wird da kopiert!

    http://www.cplusplus.com/doc/tutorial/templates/



  • Halozination schrieb:

    hi, lese gerade ein tutorial durch indem folgendes vorkommt:

    template <class T, class U>
    T GetMin (T a, U b) {
      return (a<b?a:b);
    }
    

    Wenn ich nun Die funktion getmin mit 2 verschiedenen typen T und U aufrufe kann sie doch nur einen typen T returnen? also wenn b returnt werden soll sollte es eigentl. nicht mögl sein (mit korrektem typ)?

    weitere Aufgabe: dekliniere return:
    ich returne / du returnst / er,sie,es returnt / wir returnen / ihr returnt / sie returnen



  • return ist ein Verb, das wird also konjugiert.



  • [Klugscheißen]Ja, da aber to return zweifelsohne aus dem Englischen kommt, müsstest du es dann auch nach englischen Regeln konjugieren. Da das aber komisch klingt, vermeidet man das einfach und benutzt das deutsche Wort: zurückgeben. ;)[/Klugscheißen]



  • Halozination schrieb:

    habe dein beispiel aber noch nicht ganz verstanden, was passiert in den folgenden zeilen am besten befehl für befehl.

    Das ist eine Typdefinition für einen Datentyp mit einem (öffentlich zugänglichen) int-Element und zwei Konstruktoren. Der erste Konstruktor übernimmt eine andere Struktur und wird bei der Parameter-Umsetzung für den Vergleichsoperator genutzt (um aus dem an die GetMin() übergebenen A ein B zu machen), der zweite für die Initialisierung des B myB in der main().

    PS: Um dieses Template so zu erweitern, daß es den "besseren" der beiden Template-Parameter zurückgibt, benötigst du vermutlich tiefere Kenntnisse in Template-Metaprogrammierung. Obwohl, eventuell lässt sich in C++0x mit decltype() etwas derartiges ausdrücken.


  • Mod

    könnte z.B. so aussehen (ungetested):

    template <typename T, typename U>
    struct select_best : boost::mpl::if_c<
        std::numeric_limits<T>::is_specialized
            && std::numeric_limits<U>::is_specialized
            && std::numeric_limits<T>::is_exact == std::numeric_limits<U>::is_exact
            && std::numeric_limits<T>::is_signed == std::numeric_limits<U>::is_signed,
        boost::mpl::if_c<
            std::numeric_limits<T>::is_signed,
            boost::mpl::if_c<
                std::numeric_limits<T>::digits < std::numeric_limits<U>::digits,
                U,
                T>,
            boost::mpl::if_c<
                std::numeric_limits<U>::digits < std::numeric_limits<T>::digits,
                U,
                T> >,
        void >
    {};
    
    template <typename T, typename U>
    typename select_best<T,U>::type GetMin(T a, U b)
    {
        typedef typename select_best<T,U>::type result_type;
        sizeof( result_type );
        return b < a ? static_cast<result_type>( b ) : static_cast<result_type>( a );
    }
    


  • CStoll schrieb:

    PS: Um dieses Template so zu erweitern, daß es den "besseren" der beiden Template-Parameter zurückgibt, benötigst du vermutlich tiefere Kenntnisse in Template-Metaprogrammierung. Obwohl, eventuell lässt sich in C++0x mit decltype() etwas derartiges ausdrücken.

    decltype wird da auch nicht helfen, man braucht so oder so ein genauigkeit-vergleichendes type-trait, und wenn man das hat ist decltype auch nicht mehr nötig.



  • Ich erlaube mir mal ganz themenfremd zu sagen: „Jodocus, sie sind raus!“ 😉



  • ok pumuckl habe mir geraden och einmal dein beispiel mit erklärung durchgelesen und glaube nun versteh ich es, echt ziemlich verrückt. Der compiler macht also aus myA ein B und myB ein C (ohne es ihm zu sagen) weil er sonst den < operator nicht ausführen kann und desshalb muss diese anderen constructor dabei haben

    B(A const& a) : i(a.i) {}
    

    . man könnte es dem compiler aber eigentl auch sagen oder den vergleich direkt für A und B structs erstellen 😃

    noch eine frage:

    B struct = i von der ursprüngl. 
    [cpp]
    struct B {
      int i;
      B(int i) : i(i) {} //ich hätte geschrieben B(int j) {i = j} 
    };
    ... 
    B myB = 12; //myB (myB (12)
    

    Was genau bedeutet ":" nach dem constructor? ich betrachte constructoren nur als funktion und deine schreibweise tut was genau? i(i) sieht für mich zieml. verwirrend aus.



  • Halozination schrieb:

    ok pumuckl habe mir geraden och einmal dein beispiel mit erklärung durchgelesen und glaube nun versteh ich es, echt ziemlich verrückt. Der compiler macht also aus myA ein B und myB ein C (ohne es ihm zu sagen) weil er sonst den < operator nicht ausführen kann und desshalb muss diese anderen constructor dabei haben

    Ja, das sind einige Dinge, die da zusammenfinden. Was mein Beispiel eigentlich verdeutlichen sollte: der Compiler nimmt die beiden Typen, die er ja aus der Substitution der Templateparameter kennt, und versucht damit die Ausdrücke im Template zu compilieren.
    Wenn er auf den operator< trifft (oder allgemein auf irgendeine Funktion), und die Argumenttypen passen nicht exakt zu einer Funktion, die es schon gibt, dann fängt er an, durch implizite Konvertierungen an den Typen rumzubiegen, bis er einen passenden Satz gefunden hat, der zu einer bestehenden Funktionssignatur passt. Das tut er übrigens nicht nur bei Templates, sondern eigentlich immer. Beispiel:

    int foo(int);
    short s;
    foo(s); //der compiler findet kein foo(short), aber short kann er ja in int konvertieren...
    

    Halozination schrieb:

    man könnte es dem compiler aber eigentl auch sagen oder den vergleich direkt für A und B structs erstellen 😃

    klar könnte man. Aber man muss es ihm nicht direkt sagen, denn er kanns ja implizit machen. Das Beispiel zeigt übrigens eine der großen Gefahren von impliziten Konvertierungen, d.h. von Konstruktoren, die ein Argument haben und nicht als explizit deklariert sind, sowie von Konvertierungsoperatoren: Stell dir vor, A, B, C seien Bestandteile einer Bibliothek. Ein Anwender der Bilbiothek benutzt As und Bs in seinem Code und kennt vielleicht nichtmal das Interface von C, weiß also auch nichts von dem op<. Er vertippt sich und schreibt b<a - der Compiler schluckt das anstandslos, konvertiert wild an den Argumenten rum und macht einen Vergleich von dem der Entwickler nichtmal wusste dass er existiert. Sicher nicht das, was der Entwickler vorhatte...

    Halozination schrieb:

    noch eine frage:

    B struct = i von der ursprüngl. 
    [cpp]
    struct B {
      int i;
      B(int i) : i(i) {} //ich hätte geschrieben B(int j) {i = j} 
    };
    ... 
    B myB = 12; //myB (myB (12)
    

    Was genau bedeutet ":" nach dem constructor? ich betrachte constructoren nur als funktion und deine schreibweise tut was genau? i(i) sieht für mich zieml. verwirrend aus.

    Stichwort: initialisierungsliste. Ist dazu da, um Member und Basisklassen zu initialisieren, d.h. deren Konstruktoren aufzurufen.
    Zugegegeben, hier siehts etwas verwirrend aus. Die Zeile könnte auch heißen

    B(int iArg) : i(iArg) {}
    

    - das bedeutet, dass der Member i mit dem iArg initialisiert wird.


Anmelden zum Antworten