Einführung in die Programmierung mit Templates



  • Hi, cooler Artikel 👍 ( nochmal Grundlagen aufgefrischt 😃 )

    GPC schrieb:

    Selbstverständlich kann man auch mehrere Template-Parameter angeben:

    //Zwei Parameter, einer vom Typ T und einer vom Typ U
    template <class T, class U>
    ...
    
    template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
    ...
    

    Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:

    1. Sie dürfen nicht verändert werden
    2. Sie dürfen nur ganzzahlig sein

    Vielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).

    Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.



  • Hallo,

    Freak_Coder schrieb:

    Hi, cooler Artikel 👍 ( nochmal Grundlagen aufgefrischt 😃 )

    Vielen Dank.

    GPC schrieb:

    Selbstverständlich kann man auch mehrere Template-Parameter angeben:

    //Zwei Parameter, einer vom Typ T und einer vom Typ U
    template <class T, class U>
    ...
    
    template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
    ...
    

    Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:

    1. Sie dürfen nicht verändert werden
    2. Sie dürfen nur ganzzahlig sein

    Vielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).

    Dachte eigentlich, das sei klar, nachdem ich erwähnt habe, dass eine Auswertung zur Compilezeit erfolgt.

    Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.

    Aber ich pack's noch dazu, damit alle Klarheiten beseitigt sind.

    Mfg

    GPC



  • Guter Artikel 👍

    Allerdings komme ich bei folgendem nicht ganz mit:

    GPC schrieb:

    Bei den vorangegangenen Beispielen war es noch nicht notwendig, aber wenn man größere Klassen oder Bibliotheken implementiert, dann möchte man die Schnittstelle in eine .hpp-Datei und die Implementation in eine .cpp-Datei schreiben.
    Die Motivation dahinter ist, dass jede Änderung an einer Headerdatei dazu führt, dass all der Code neu kompiliert werden muss, der diese Headerdatei benutzt. Gerade in Projekten mit vielen Dateien löst das große Neu-Kompilier-Wellen aus, die ziemlich lange dauern können.

    Leider geht das bei Templates so nicht. Das liegt daran, dass das Template erst überall dort in den Code eingesetzt wird, wo es auch verwendet wird. Vorher ist es "nur ein Stück Text", erst beim Einsetzen bekommt es seine Bedeutung. Genau das verursacht aber das oben beschriebene Verhalten vom Neu-Kompilieren.

    Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht? Mir ist deswegen nicht ganz klar, warum man Teile der Template-Implementierung (die effektiv Teil des Templates sind) irgendwohin auslagern wollen könnte. Um auf das verwendete Beispiel einzugehen:

    //stack.hpp
    template <typename T>
    class Stack {
      //wie oben, nur die Schnittstelle
    };
    
    //Achtung:
    #include "stack.impl"
    
    //stack.impl
    template<typename T>
    inline bool Stack<T>::empty() const {
      return (tip==0) ? true : false;
    };
    //...
    

    "Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.
    Genauso kann ich aber etwas an stack.hpp ändern und werde trotzdem den Rattenschwanz der stack.impl hinter mir herziehen, weil dann mein Build-System merkt: "Aha, Header neuer als Objekt, Kompilation anstoßen", der Compiler (Präprozessor) wird merken, daß da eine eigenartige stack.impl eingefügt wird und diese korrekterweise in den header einfügen, worauf dann der Compiler wieder den vollständigen Template-Code "sieht" und das Template so oft instanziiert, wie es nunmal nötig ist.
    Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). 😕



  • tommie-lie schrieb:

    Guter Artikel 👍

    Danke.

    Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht?

    Hat sich was geändert, soll es neu kompiliert werden, korrekt.

    "Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.

    Die Trennung ist in dem Fall auch rein optischer Natur, denn am Ende inkludiere ich ja die cpp datei in die hpp Datei und damit bin ich gleich weit, wie wenn ich alles in die hpp Datei geschrieben hätte. Es ist nur eine Erleichterung der Übersicht.

    Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). 😕

    Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben? Egal.

    MfG

    GPC



  • GPC schrieb:

    Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). 😕

    Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?

    Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.
    Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen 😉
    Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.



  • Hallo,

    tommie-lie schrieb:

    GPC schrieb:

    Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). 😕

    Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?

    Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.

    schön wär's. Aber leider hat das performancetechnisch keine Auswirkungen.

    Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen 😉

    kein Thema.

    Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.

    äh, nein. Das tun sie nicht. denn die impl Datei ist ja nicht "direkt" am Build-Prozess beteiligt. Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich. Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.

    MfG

    GPC



  • GPC schrieb:

    Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich.

    Ich weiß 😉

    GPC schrieb:

    Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.

    Ich auch 😉



  • haha, okay, dann sind wir uns ja einig... 😉



  • GPC schrieb:

    haha, okay, dann sind wir uns ja einig... 😉

    Jupp. Ich bin aufgrund des ersten Absatzes nur mit ganz anderen Vorstellung an diesen Abschnitt des Tutorials herangegangen und dachte, du würdest ernsthaft denken, daß dieses Verfahren die Performance verbessert. Da dem nicht so ist (beides, es verbessert du Performance nicht und du denkst auch nicht, daß es das tun würde), bleibt die Diskussion nur eine Anmerkung für alle weiteren Leser.



  • Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art

    template<typename T>
    void f()
    {
      if (typename(T)==TypeInt)
         T++;
      foo(T);
    }
    

    danke



  • _et schrieb:

    Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art

    template<typename T>
    void f()
    {
      if (typename(T)==TypeInt)
         T++;
      foo(T);
    }
    

    danke

    Joah, du kannst es über Spezialisierungen (Abschnitt 5 bzw. 6 für Klassen) machen, aber mit ner if-Abfrage wird das nichts.

    MfG

    GPC



  • _et schrieb:

    Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen.

    Gehen tut das sicherlich so ähnlich wie du es geschrieben hast, aber der übliche Weg ist es, seine Templates zu spezialisieren:

    template<typename T>
    void foo(T arg1)
    {
      // generische Implementierung für alle Typen
    }
    
    template<>
    void foo(int arg1)
    {
      // spezielle Implementierung die nur für ints gilt
    }
    

    Syntaktisch also genauso aufgebaut wie Überladen von Funktionen.
    Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.

    Edit: Zu spät...



  • Danke 🙂



  • tommie-lie schrieb:

    Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.

    Wirklich ein sehr guter Artikel. Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.

    MfG

    GPC



  • GPC schrieb:

    Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.

    Naja, zuviel Sekundärliteratur kann auch erschlagen 😉

    Aber was mir jetzt erst in deinem Artikel auffällt:

    //Ermittelt das Minimum aus a und b
    template <typename T>
    inline const T& minimum(const T &a, const T &b) {
      return a < b ? a:b;
    };
    
    //Spezialisierung für C-Strings
    inline const char* minimum(const char *str1, const char *str2) {
      return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
    };
    

    Das ist keine Spezialisierung, das ist ein Überladen der Funktion. Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus). Die Deklaration mit und ohne den Modifier "template" macht einen semantischen Unterschied, nämlich den zwischen einer überladenen Funktion und einem überladenen Template.
    Ich denke das sollte man nochmal irgendwie klarstellen.



  • tommie-lie schrieb:

    Aber was mir jetzt erst in deinem Artikel auffällt:

    //Ermittelt das Minimum aus a und b
    template <typename T>
    inline const T& minimum(const T &a, const T &b) {
      return a < b ? a:b;
    };
    
    //Spezialisierung für C-Strings
    inline const char* minimum(const char *str1, const char *str2) {
      return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
    };
    

    Das ist keine Spezialisierung, das ist ein Überladen der Funktion.

    Steht im Satz drunter:;

    GPC schrieb:

    Eigentlich haben wir jetzt die Funktion minimum überladen, um unser Ziel, also die Spezialisierung, zu erreichen.

    Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus).

    IIRC macht auch der gcc 3.3.6 keinen Stress, dennoch ist der Code imho nicht ANSI konform, ich werde meine Quelle nachschlagen und posten, voraussichtlich morgen.

    MfG

    GPC



  • Wie versprochen, wenn auch verspätet, meine Quelle:

    Prinz Peter, kirch-Prinz Ulla: C++ Lernen und professionell anwenden, 2. vollständig überarbeitete Auflage, Bonn 2002 (mitp), S. 760:

    Im ANSI-Standard werden Template-Funktionen und "normale" Funktionen nicht unterschieden. Die Definition eines Funktions-Template und einer Funktion gleichen Namens, die auch aus dem Funktions-Template generiert werden könnte, führt daher zu einer Fehlermeldung des Compilers ("duplicate definition ...").

    Deshalb sieht der ANSI-Standard eine eigene Syntax vor, um Spezialisierungen zu definieren:

    #include <cstring>
    
    template<>
    const char* min( const char* s1, const char* s2 )
    {
        return ( (strcmp(s1, s2) < 0 ) ? s1: s2 );
    }
    

    Ich gehe davon aus, dass ich mich darauf verlassen kann, obwohl ich in diesem Buch schon an anderer Stelle einen gravierenden Fehler entdeckt habe. Ich wollte das damals eigentlich noch im Standard nachschlagen, aber dann fehlte mir die Zeit dazu, so blieb es "ungeprüft" drin.

    Dennoch bleibe ich solange bei meiner Meinung, bis mir jemand anderes das Gegenteil beweist, oder ich selber herausfinde, das ich falsch lag. Ich überlasse es dir, den Gegenbeweis zu erbringen.

    MfG

    GPC



  • Im ANSI-Standard werden Template-Funktionen und "normale" Funktionen nicht unterschieden.

    Bei sehr strenger Auslegung soweit nix falsches.

    Deshalb sieht der ANSI-Standard eine eigene Syntax vor, um Spezialisierungen zu definieren:

    Richtig, das sind Template-Spezialisierungen (oder template overloading) im Gegensatz zu neuen Funktionsdeklarationen.

    Nun, zum einen würde die Auflösungsregel ja überhaupt keinen Sinn ergeben, wenn dem tatsächlich so wäre. Du nennst folgende Auflösungsregeln:

    1. Findet der Compiler eine normale Funktion, die er ohne Typumwandlung aufrufen kann, so wird diese aufgerufen
    2. Wenn es ein spezialisiertes Template (zusätzlich zu der generischen Implementierung) gibt, dann nimmt der Compiler immer dieses, gibt es mehrere spezialisierte Templates, so wählt er das am meisten Spezialisierte aus
    3. Konnte keine passende Funktion gefunden werden, so werden normale Funktionen überprüft, bei denen Typumwandlungen zum Erfolg führen

    Denen widerspreche ich nicht, weil ich sie auch so kenne und weil sie auch in dem von mir verlinkten Sutter-Artikel stehen.
    Regel 1 würde aber überhaupt keinen Sinn machen, wenn ich keine normale Funktion gleichen namens wie ein Funktionstemplate im gleichen Namespace deklarieren kann. Wenn ich aus jeder Funktion, die genauso heißt, durch voranstellen eines "template<>" ohnehin ein Funktionstemplate machen muss, dann würde der Compiler gar nicht erst nach einer "normalen Funktion, die er ohne Typumwandlung aufrufen kann" suchen, sondern würde nach einer passenden Templatespezialisierung suchen oder im Template den Typen eintragen.

    Da ich mir den Standard nicht gekauft habe, greife ich auf ein Working PAper von 1996 zurück, in dem es in 14 Absatz 5 heißt:

    The name of a class template shall not be declared to refer to any other template, class, function, object, enumeration, enumerator, namespace, or type in the same scope. Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name, a template name declared in namespace scope shall be unique in that namespace.

    Mit anderen Worten: Ja, es gelten die gleichen grundsätzlichen Regeln wie bei gewöhnlichen, Nicht-Templatefunktionen, aber auch gewöhnliche Funktionen kann ich mit gleichem Namen aber unterschiedlicher Signatur überladen.

    Weiterhin heißt es in Kapitel 14.8.3:

    2 [Example:

    template<class T> T max(T a, T b) { return a>b?a:b; };
    
              void f(int a, int b, char c, char d)
              {
                      int m1 = max(a,b);  // max(int a, int b)
                      char m2 = max(c,d); // max(char a, char b)
                      int m3 = max(a,c);  // error: cannot generate max(int,char)
              }
    

    3 Adding

    int max(int,int);
    

    to the example above would resolve the third call, by providing a
    function that could be called for max(a,c) after using the standard
    conversion of char to int for c.

    Ich nehme nicht an, daß der Standard vorschlagen würde, eine gewöhnliche Funktion gleichen namens und gleicher Signatur(!) wie eine mögliche Templatefunktion einzuführen und dabei sogar implizite Typkonvertierung zu benutzen, wenn dies illegal wäre.

    GPC schrieb:

    tommie-lie schrieb:

    Das ist keine Spezialisierung, das ist ein Überladen der Funktion.

    Steht im Satz drunter:;

    GPC schrieb:

    Eigentlich haben wir jetzt die Funktion minimum überladen, um unser Ziel, also die Spezialisierung, zu erreichen.

    Warum nennt man es dann im Kommentar im Code und im Absatz darüber "Spezialisierung"?
    Der Standard kennt einen Unterschied zwischen Überladung und Spezialisierung, ich finde, den sollte man auch kennzeichnen und die Begriffe nicht synonym verwenden. Um so verwirrender ist der von dir zitierte Satz ja auch noch, wenn gleich darauf gesagt wird, daß das illegal wäre und man ein "template<>" davor setzen muss, was die Funktion templatisieren würde und somit eine echte Template-Spezialisierung hervorbringen würde.
    Meiner MEinung nach wird da einfach zu sehr mit Begriffen durcheinandergewürfelt, die nicht durcheinandergewürfelt gehören, weil sie vollkommen unterschiedliche Auswirkungen auf das Verhalten des Compilers haben (siehe der Sutter-Artikel und deine Auflösungsregeln für Funktionsnamen, die am Anfang des Artikel-Abschnitts steht).



  • Hallo,

    hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:

    14.5.5 Function templates

    A function template can be overloaded with other function templates and with normal (non-template) func-
    tions. A normal function is not related to a function template (i.e., it is never considered to be a specializa-
    tion), even if it has the same name and type as a potentially generated function template specialization. 130)

    __________________
    130) That is, declarations of non-template functions do not merely guide overload resolution of template functions with the same name.
    If such a non-template function is used in a program, it must be defined; it will not be implicitly instantiated using the function tem-
    plate definition.

    14.5.5.1 Function template overloading
    It is possible to overload function templates so that two different function template specializations have the
    same type. [Example:

    // file1.c                               // file2.c
           template<class T>                        template<class T>
                void f(T*);                                void f(T);
           void g(int* p) {                         void h(int* p) {
                f(p); // call                              f(p); // call
                      // f<int>(int*)                            // f<int*>(int*)
           }                                        }
    

    -end example]
    Such specializations are distinct functions and do not violate the one definition rule (3.2).
    The signature of a function template specialization consists of the signature of the function template and of
    the actual template arguments (whether explicitly specified or deduced).

    14.7 Template instantiation and specialization

    An explicit specialization may be declared for a function template, a class template, a member of a class
    template or a member template. An explicit specialization declaration is introduced by template<>.

    Warum nennt man es dann im Kommentar im Code und im Absatz darüber "Spezialisierung"?

    In der Überschrift steht klar Überladung (Spezialisierung), es muss wohl ein Flüchtigkeitsfehler gewesen sein.

    Der Standard kennt einen Unterschied zwischen Überladung und Spezialisierung, ich finde, den sollte man auch kennzeichnen und die Begriffe nicht synonym verwenden. Um so verwirrender ist der von dir zitierte Satz ja auch noch, wenn gleich darauf gesagt wird, daß das illegal wäre und man ein "template<>" davor setzen muss, was die Funktion templatisieren würde und somit eine echte Template-Spezialisierung hervorbringen würde.

    Nun, ich anerkenne, dass es einen Unterschied zw. der Überladung und der *echten* Spezialisierung gibt. Dieser Unterschied wird im Artikel tatsächlich nicht ausreichend dargestellt.

    Ich werde den betreffenden Abschnitt abändern, sobald ich Zeit dafür finde. Okay?

    MfG

    GPC



  • GPC schrieb:

    hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:

    14.5 habe ich doch auch zitiert :p

    GPC schrieb:

    Okay?

    Du musst mit mir nicht verhandeln, es ist dein Artikel. Ich wollte lediglich Fehler (oder was meiner Meinung nach welche sind) aufzeigen 🙂


Anmelden zum Antworten