Einführung in die Programmierung mit Templates
-
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ührenDenen 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
-
tommie-lie schrieb:
GPC schrieb:
hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:
14.5 habe ich doch auch zitiert :p
Joah, aber der war mir zu allgemein, ich fand die Beschreibung mit 14.5.5 und 14.5.5.1 exakter und aufschlussreicher...
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
Es sind Fehler, daher werde ich sie auch korrigieren. Jeder will hier schließlich gute Arbeit abliefern, wenn auch verspätet^^
Wir sind sogar auf aufmerksame Leser angewiesen, wenn wir Fehler finden wollen, die die interne Revision überlebt haben.MfG
GPC
-
Okay, Abschnitt umgeschrieben
-
GPC schrieb:
Okay, Abschnitt umgeschrieben
Besser!
Aber nur um bei den vorhandenen Beispielen zu bleiben und anhand eines Beispiels alles zu erklären könnte man die Überladung von minimum<>() auch für C-Strings zeigen.
Also nach dem Motto:Eine echte Spezialisierung unseres minimum<>()-Templates für Strings würde demnach also so aussehen:
template<> inline const char *&minimum(const char *&a, const char *&b) { return ((strcmp(a, b) < 0) ? b : a); };
-
Hem, ich dachte ich bring mal was anderes als das ausgelutschte minimum... aber in diesem Kontext verdeutlicht es die Spezialisierung wohl besser.
-
Sorry, aber mein Code war auch noch falsch.
Dein Template verlangt "const T &", also eine konstante Referenz. Meine "char *"-Spezialisierung gibt ihm aber eine nicht-konstante Referenz auf einen Pointer auf einen const char. Sowas passiert, wenn man mal eben schnell ohne Compiler ein Template zusammensetzt und noch nicht ganz wach ist...
Das Template mit Inline mit const und allem drum und dran wird für einen Anfänger in dieser Form vielleicht etwas haarig, aber richtig (und mit Erklärung) wäre die Spezialisierung für "char *" folgendermaßen:template<> inline const char *const &minimum(const char *const &a, const char *const &b) { return ((strcmp(a, b) < 0) ? b : a); };
Man könnte zwar Denken, daß ein "const char *&a" ausreichen sollte, wenn man für T einfach stupide ein "char *" einsetzt, aber so denkt der C++-Standard nicht. Die Typen im generischen Template sind "const T&", was einer konstanten Referenz auf T entspricht. Wollen wir das gleiche mit einem Pointer erreichen, darf nicht das Ziel des Pointers const sein, sondern die Referenz. Somit würde eine Template-Spezialisierung für "char *" folgendermaßen aussehen:
[cpp]template<> inline char *const &minimum(char *const &a, char *const &b) { return ((strcmp(a, b) < 0) ? b : a); };
Die Typen entsprechen einer konstanten Referenz auf einen Zeiger auf einen char. Natürlich kann man dadurch den String selbst noch verändern, um also auch noch den char konstant zu deklarieren, benötigen wir das zweite const, was insgesamt den Typ "const char *const &" ergibt. Die Spezialisierung ist nun "template<const char *>minimum(), also eine Spezialisierung für "const char *" und nicht mehr nur für "char *".
Ist zwar sehr verwirrend, aber durch "const T &" legst du ja schon relativ verwirrungsträchtig vor
Ist ein wenig ins Unreine geschrieben, die perfekte Integration in deinen Artikel überlasse ich dir (hängt auch davon ab, wie sehr du deinen Artikel über Templates auch die "const correctness" beleuchten lassen willst).
-
Sorry, aber mein Code war auch noch falsch.
Tjo, mir ist's in der Eile auch nicht aufgefallen, das kommt von Copy & Paste^^
Deine Erklärung ist gut, jedoch befürchte ich, dass es an der Stelle etwas zu viel wird, zum Einen das Template und dann noch die const-correctness (die ich übrigens in meinem Pointer-Artikel erläutert habe ). Daher bin ich wieder auf das einfache Beispiel umgestiegen.
Du bist ziemlich fit, was C++ angeht, besonders Templates... darf ich dich hierauf aufmerksam machen? Na wie sieht's aus, hättest du Zeit und Lust für den Artikel?
MfG
GPC
-
GPC schrieb:
jedoch befürchte ich, dass es an der Stelle etwas zu viel wird
Ja, ich auch.
GPC schrieb:
zum Einen das Template und dann noch die const-correctness (die ich übrigens in meinem Pointer-Artikel erläutert habe ).
Wenn die Geschichte mit Referenzen auf Konstanten in dem Artikel erklärt wird, würde ich für jeden, der wissen will, was "const char *const &" bedeutet, dorthin verlinken. Früher oder später wird man ohnehin const-correctness kapieren müssen
GPC schrieb:
Du bist ziemlich fit, was C++ angeht, besonders Templates...
Geht so. Ich scheine nur der einzige zu sein, der bei Diskussionen "C++ gegen den Rest der Welt" immer der Meinung ist, C++ habe eine einfach zu verstehende Syntax.
GPC schrieb:
darf ich dich hierauf aufmerksam machen? Na wie sieht's aus, hättest du Zeit und Lust für den Artikel?
Habe ich so noch nicht mit gearbeitet, aber die Beispiele im Wikipedia-Artikel haben was... (sind allerdings in dieser Formatierung ohne Highlighting ziemlich unleserlich ;)). Außerdem habe ich nicht vor, mir auch noch großartig Bücher zu kaufen. Und ich müsste mir erstmal einen Algorithmus aus den Fingern saugen, der vom Verständnis her einfach ist und der sich auch syntaktisch nicht allzu verwirrend in C++-Templates gießen lässt. Wie gesagt, schon für das Potenzierungs-Beispiel von Wikipedia brauchte ich einen Syntaxhighlighter und zweimaliges Lesen (die Fakultät im englischen Aritkel war da deutlich einfacher).
Vielleicht kriege ich ja passende Beispiele serviert, wenn ich studiere.
Aber ich biete mich gerne zum Gegenlesen an, wenn einer einen Artikel hat.
-
Hi,
hab das gerade mal nen bisschen ausprobiert, aber folgendes verstehe ich nicht:
#ifndef MY_CLASS_H #define MY_CLASS_H #include <iostream> template <typename T> class MyClass { private: T m_Element; public: MyClass( void ) { ; } ~MyClass( void ) { ; } void setElement( T NewElement ) { std::cout << "[Typ]NewElement: ???" << std::endl; m_Element = NewElement; } //template< > // Wie funktioniert das nun!? void setElement( int NewElement ) { std::cout << "[Typ]NewElement: int" << std::endl; m_Element = NewElement; } T getElement( void ) { return m_Element; } void PrintElement( void ) { std::cout << "Element: " << m_Element << std::endl; } }; #endif /* MY_CLASS_H */
Bei mir compiliert der Code, aber so solls ja glaube nicht gemacht werden, ohne das template < >?
Danke
Gruß
-
Was willst du überhaupt machen? Die Methode setElement für int spezialisieren?
-
Richtig. Genau das. Ich weiß, dass das schwachsinnig ist, aber irgendwie muss ich das ja ausprobieren nur klappt das nicht...
/edit by GPC: nick gekürzt
-
Na ja, mir fehlt etwas der konkrete Anwendungsfall, aber wenn du z.B. den Code ausführst:
//hier dein Code int main() { MyClass<float> fc; fc.setElement(2.5f); fc.setElement(2); }
dann gibt's folgende Ausgabe:
gpc@desktop:~$ g++ -o main main.cpp gpc@desktop:~$ ./main [Typ]NewElement: ??? [Typ]NewElement: int
Btw. Bitte verwende einen kürzeren Nick. Danke
MfG
GPC
-
So habe ich aber keine Spezialisierung sondern nur eine normale Überladung. Kann doch nicht richtig sein?
-
Stimmt, das ist Überladung. Da du aber ein Klassentemplate hast, kannst du auch nur das spezialisieren (partiell oder vollständig). D.h. entweder die Überladung oder du machst es so:
//Generisch: template <typename T> struct Foo { T bar; void set_bar(const T &x) { bar = x; } const T& get_bar() const { return bar; } }; //Für ints: template <> struct Foo<int> { //hier spezialisierte Versionen der Methoden von oben };
Damit hast du allerdings die Klasse spezialisiert.
Template-Methoden spezialisieren geht auch, aber dann brauchst du template-Methoden.