Templatespezialisierung vs. Überladene Funktion
-
Ich würde grundsätzlich einen Overload vorziehen.
-
Unbedingt den Overload vorziehen:
http://www.gotw.ca/publications/mill17.htm
-
@hustbear, theta
Könnte ihr das nochmal näher begründen?Ich weiß nicht, der Artikel hat in meinen Augen nicht sonderlich viel Qualität. Warum soll es nicht intuitiv sein, dass zuerst das Template ausgewählt wird und danach erst die jeweiligen Spezialisierungen berücksichtigt werden?
Wenn ich jetzt z.B. hergehe und meinem Template statt einer Spezialisierung eine normale Funktion verpasse, dann ist es ja auch mit der Template-Überladung für diesen Datentyp vorbei. Besonders intuitiv ist das auch nicht.
template<class T> // mein Template void f( T ); //template<> // Spezialisierung, nehme ich nicht //void f<>(int*); void f(int*); // stattdessen eine normale Funktion template<class T> // obwohl ich nun das Template für Zeiger überlade void f( T* ); // bleibt es beim alten Verhalten im Fall int*Macht auch nicht gerade viel Sinn. Jedenfalls bräuchten in meinen Augen die von euch verwendeten Begriffe***"grundsätzlich Overload"*** und "unbedingt Overload" nochmal eine Begründung, vielleicht wird es dann klarer.
Der Threadstarter hat ja schließlich auch nach Vor- und Nachteilen gefragt.

-
template<typename T> void foo(T) { std::cout << "T" << std::endl; } template<> void foo(int*) // Gilt als Spezialisierung von template<typename T> void foo(T); { std::cout << "int*" << std::endl; } template<typename T> void foo(T*) { std::cout << "T*" << std::endl; } // ... int* p = 0; foo(p); // gibt T* aus, da das Template T* besser ist als das Template T. // Ich wollte aber die Spezialisierung haben, die wurde allerdings nicht gefunden, // da sie zum Template T gehört. template<typename T> void foo(T) { std::cout << "T" << std::endl; } void foo(int*) // Keine Spezialisierung, sondern Überladung. { std::cout << "int*" << std::endl; } template<typename T> void foo(T*) { std::cout << "T*" << std::endl; } // ... int* p = 0; foo(p); // Gibt wie gewünscht int* aus. Funktionen werden Funktionstemplates bevorzugt.Das grosse Problem hier ist also, worauf sich die Spezialisierung bezieht. Eine Überladung gilt für alle Funktionen, eine Spezialisierungen immer nur explizit auf ein Funktionstemplate davor.
Grüssli
-
Dravere schrieb:
int* p = 0; foo(p); // gibt T* aus, da das Template T* besser ist als das Template T. // Ich wollte aber die Spezialisierung haben, die wurde allerdings nicht gefunden, // da sie zum Template T gehört.Das sehe ich etwas anders. Warum sollte man intuitiv die Spezialisierung erwarten?
Ich würde eher intuitiv das für Zeigertypen überladene Template erwarten. Besonders wenn das erste Template nicht von mir stammt und ich mein eigenes für Zeigertypen selbst hinzugefügt hätte.
Macht doch viel mehr Sinn, oder nicht?
-
Mitleid schrieb:
Macht doch viel mehr Sinn, oder nicht?
In meiner Logik überhaupt nicht. C++ nimmt normalerweise immer die Funktion, welche am besten passt. Du hast eine Funktion, welche explzit einen
int*nimmt, besser also gar nicht passen kann, aber sie wird nicht genommen. Sowas halte ich für sehr seltsam.Auch dein Beispiel ist etwas an den Haaren herbeigezogen. Es kann natürlich schon sein, dass man eine Spezialisierung möchte und keine Überladung, aber das kommt meiner Meinung nach eher sehr selten vor. Habe ich persönlich jedenfalls noch nicht ein einziges Mal erlebt.
Grüssli
-
Dravere schrieb:
Du hast eine Funktion, welche explzit einen
int*nimmt, besser also gar nicht passen kann, aber sie wird nicht genommen. Sowas halte ich für sehr seltsam.Moment, mit einer Spezialisierung hast du keine Funktion die ein int* nimmt sondern einen Sonderfall, für den das allgemein gehaltene Template ungeeignet ist.
Wenn ich das Template für bestimmte Datentypen überladen will, dann erwarte ich selbstverständlich, dass zu einem Aufruf das spezialisierteste Template genommen wird und nicht der Sonderfall eines weniger spezialisierten Templates.
Dravere schrieb:
Auch dein Beispiel ist etwas an den Haaren herbeigezogen. Es kann natürlich schon sein, dass man eine Spezialisierung möchte und keine Überladung, aber das kommt meiner Meinung nach eher sehr selten vor. Habe ich persönlich jedenfalls noch nicht ein einziges Mal erlebt.
Welches Beispiel? Was meinst du jetzt genau?
-
Dravere schrieb:
Es kann natürlich schon sein, dass man eine Spezialisierung möchte und keine Überladung, aber das kommt meiner Meinung nach eher sehr selten vor. Habe ich persönlich jedenfalls noch nicht ein einziges Mal erlebt.
Kann unter anderem Sinn machen, wenn man Template-Argumente explizit angibt. Also sowas wie:
std::string str; int a = super_string_cast<int>(str); // int wird mit allgemeinem Template behandelt float b = super_string_cast<float>(str); // ok, float ebenfalls MyClass c = super_string_cast<MyClass>(str); // MyClass erfordert spezielle Behandlung, hier SpezialisierungAber da Funktionstemplates nicht partiell spezialisiert werden können, ist man in vielen Fällen ziemlich eingeschränkt. Zum Glück kann man mit Überladung trotzdem recht viel erreichen...
-
Mitleid schrieb:
Moment, mit einer Spezialisierung hast du keine Funktion die ein int* nimmt sondern einen Sonderfall, für den das allgemein gehaltene Template ungeeignet ist.
Ja, klar, das ist ja der Unterschied und dieser ist meiner Meinung nach nicht so logisch ersichtlich. Es ist nicht wirklich klar, für was die Spezialisierung gilt, da man sich in C++ und Funktionen stark am Namen orientiert. Dass sich diese Zugehörigkeit ganz unterschiedlich verhalten kann, wird auch schön im Artikel aufgezeigt, wo die Reihenfolge der Deklaration unterschiedlich ist. Es ist eben keine Überladung, obwohl man sowas durchaus erwarten könnte.
Vor allem sollte man sich auch bewusst werden, dass die Überladung und Spezialisierung über mehrere Dateien verteilt sein kann. Da noch den Überblick zu behalten, was nun wohl eine Spezialisierung für welche Überladung sein wird, das dürfte eher schwer sein und der Übersicht nicht wirklich dienlich. Vor allem auch, weil man doch meistens die Überladung möchte, sonst nimmt man am besten gleich einen anderen Funktionsnamen.Deshalb auch die Empfehlung, dass man die Überladung vorziehen sollte. Es steht nicht, dass man die Spezialisierung vergessen sollte

Mitleid schrieb:
Welches Beispiel? Was meinst du jetzt genau?->
Mitleid schrieb:
Besonders wenn das erste Template nicht von mir stammt und ich mein eigenes für Zeigertypen selbst hinzugefügt hätte.
Empfinde ich nicht wirklich als übliches Vorgehen. Meistens sind fremde Codes auch noch in einem eigenen Namensraum und in fremde Namensräume sollte man eher nichts reinsetzen. Für sowas sollte man besser eine explizite Trennung durchführen. Ich habe jedenfalls sowas noch nicht gesehen und kann mir auch kein praktisches Beispiel dazu ausdenken.
Grüssli
-
Ich fürchte wir werden uns da nicht einig und das Problem ist praktisch auch mehr oder weniger bedeutungslos. Immerhin wurden ein paar Aspekte angeschnitten.
Ich hoffe das war im Sinne des Threadstarters.Nebenbei wenn du nur eine normale Funktion hinzufügst kann ein eifriger Programmierer für seinen Aufruf trotzdem das Template instanziieren (siehe Beitrag Nexus)
template<typename T> void foo(T) { std::cout << "T" << std::endl; } void foo(int*) // Keine Spezialisierung, sondern Überladung. { std::cout << "int*" << std::endl; } template<typename T> void foo(T*) { std::cout << "T*" << std::endl; } // ... int* p = 0; foo(p); // Gibt wie gewünscht int* aus. Funktionen werden Funktionstemplates bevorzugt. foo<int*>(p); // Template behandelt den Fall falsch und *Puff*!Um auch die Meinungsverschiedenheit unter den "Kapazitäten" zu verdeutlichen hier ein Zitat von Lippman aus dem Primer:
Best Practices: It is hard to design overloaded function sets involving both function templates and nontemplate functions. Because of the likelihood of surprise to users of the functions, it is almost always better to define a function-template specialization than to use a nontemplate version.
Aber gut, wie gesagt, das Problem ist von niedriger Bedeutung. Ich klinke mich fürs Erste aus und genieße noch etwas die Sonne.

-
@Mitleid:
Du solltest den hier verlinkten Artikel http://www.gotw.ca/publications/mill17.htm nochmal lesen, denn du hast ihn offensichtlich nicht verstanden.
Speziell:// A class template template<class T> class X { /*...*/ }; // (a) // A function template with two overloads template<class T> void f( T ); // (b) <------ DAS HIER ----- template<class T> void f( int, T, double ); // (c) // ... // Example 1, continued: Specializing templates // // A partial specialization of (a) for pointer types template<class T> class X<T*> { /*...*/ }; // A full specialization of (a) for int template<> class X<int> { /*...*/ }; // A separate base template that overloads (b) and (c) // -- NOT a partial specialization of (b), because // there's no such thing as a partial specialization // of a function template! template<class T> void f( T* ); // (d) <------ UND DAS HIER ----- // A full specialization of (b) for int template<> void f<int>( int ); // (e) // A plain old function that happens to overload with // (b), (c), and (d) -- but not (e), which we'll // discuss in a moment void f( double ); // (f)(d) ist KEINE Spezialisierung!
Und: wieso sollte man hier nochmals alles erklären, was in dem Artikel wunderbar beschrieben steht?
-
hustbaer schrieb:
@Mitleid:
Du solltest den hier verlinkten Artikel http://www.gotw.ca/publications/mill17.htm nochmal lesen, denn du hast ihn offensichtlich nicht verstanden.Offensichtlich ist nur, dass du froh bist von theta den Artikel verlinkt bekommen zu haben, denn sonst hättest du keine Argumente für deine Behauptung "grundsätzlich Overload" vorbringen können.
Ich bilde mir ein den Artikel verstanden zu haben (er ist ja auch recht leicht zu verstehen), bin aber mit den dort vertretenen Meinungen nicht 100%ig einverstanden. Die Semantik der Sprache ist nun mal wie sie ist, deshalb finde ich es übertrieben etwas wie
Moral #1: If you want to customize a function base template and want that customization to participate in overload resolution (or, to always be used in the case of exact match), make it a plain old function, not a specialization. And, if you do provide overloads, avoid also providing specializations.
zu empfehlen, nur weil man angeblich intuitiv eine andere Semantik erwartet.
hustbaer schrieb:
(d) ist KEINE Spezialisierung!
Danke für die deutliche Klarstellung, ich glaube aber das war mir und allen die hier mitdiskutieren bereits klar.

Wenn dich mein Satz "...dass zu einem Aufruf das spezialisierteste Template..." zu der irrigen Ansicht gebracht, dann stelle ich nochmal klar, dass das spezialisierteste Template bei der Argumentdeduktion gemeint war.
hustbaer schrieb:
Und: wieso sollte man hier nochmals alles erklären, was in dem Artikel wunderbar beschrieben steht?
Nun ich nahm an, wenn du sagst, dass man GRUNDSÄTZLICH mit einer normalen Funktion überladen sollte, dann hast du auch ein paar gute Argumente dafür.
Was in dem Artikel gesagt wird ist schön und gut, nur, wie bereits aufgeführt, sind andere Autoren auch gegensätzlicher Meinung. Es scheint also nicht ganz so eindeutig zu sein.
Und was du mit einem Aufruf wie
int* p = 0; f<int*>(p);in deiner Variante machen willst, hast du auch noch nicht verraten.
Wie oben bereits erwähnt ist das Problem in meinen Augen eher nebensächlich. Ich würde die Wahl als Geschmackssache abtun. Aber, den Quelltext gut kommentieren, für Leute die eine andere Intuition haben.

-
Mitleid schrieb:
hustbaer schrieb:
@Mitleid:
Du solltest den hier verlinkten Artikel http://www.gotw.ca/publications/mill17.htm nochmal lesen, denn du hast ihn offensichtlich nicht verstanden.Offensichtlich ist nur, dass du froh bist von theta den Artikel verlinkt bekommen zu haben, denn sonst hättest du keine Argumente für deine Behauptung "grundsätzlich Overload" vorbringen können.
Ich kannte genau diesen Artikel schon, war nur zu faul mir den Link wieder rauszusuchen.
Mitleid schrieb:
Und was du mit einem Aufruf wie
int* p = 0; f<int*>(p);in deiner Variante machen willst, hast du auch noch nicht verraten.
Ich will in "meiner" Variante garnix damit machen, weil das IMO Mist ist. Bei einem Funktionstemplate Template-Argumente mitzugeben, wenn diese deduziert werden können, ist IMO so unsauber, dass man darauf keinerlei Rücksicht nehmen muss.
BTW: bist du dir darüber im Klaren, dass bei deiner Variante hier das Template "template<typename T> void foo(T)" aufgerufen wird, und NICHT "template<typename T> void foo(T*)"? Und genau sowas halte ich für total kontraintuitiv.
----
IMO gibt es nur zwei sinnvolle Varianten, da dabei keine Überraschungen entstehen.
- nur Overloads, keine einzige Spezialisierung
- nur ein einziges Template, welches an eine statische Non-Template Member-Funktion einer Template Klasse weiterleitet, und dann die Template-Klasse (u.u. auch partiell) spezialisieren
Möglichkeit 3), nämlich nur Spezialisierungen halte ich im Allgemeinen nicht für gut, da man Funktions-Templates eben nicht partiell spezialisieren kann.
Und Möglichkeit 4), nämlich Spezialisierungen + Overloads dort wo partielle Spezialisierungen nötig wären, halte ich für eine Katastrophe schlechthin, da dann wirklich Dinge passieren, die man sich nicht erwartet.
----
Es gibt allerdings einige Spezialfälle, in denen "nur Spezialisierungen" Sinn machen können. Nämlich z.B. dann, wenn man eine Funktion für bestimmte Typen "erlauben" will, und gleichzeitig implizite Konvertierungen verbieten.
Beispiel:template <class T> void foo(T const&); template <> void foo(int const&) {} template <> void foo(double const&) {} void test() { foo(1); // geht, int foo(1.0); // geht auch, double foo(1.0f); // geht nicht, float foo(1L); // geht nicht, long foo(1U); // geht nicht, unsigned int // ... }----
p.S.: noch ein lustiges Beispiel dafür, wieso Funktions-Template-Spezialisierungen böse sind:
template <class T> void Foo(T) { printf("primary template, T = %s\n", typeid(T).name()); } template <> void Foo(int const*) { printf("specialization int const*\n"); } int _tmain(int argc, _TCHAR* argv[]) { Foo(static_cast<char const*>(0)); int i = 0; Foo(&i); // OOPS! int const ic = 0; Foo(&ic); return 0; } // Output: /* primary template, T = char const * primary template, T = int * specialization int const* */
-
hustbaer schrieb:
Ich will in "meiner" Variante garnix damit machen, weil das IMO Mist ist. Bei einem Funktionstemplate Template-Argumente mitzugeben, wenn diese deduziert werden können, ist IMO so unsauber, dass man darauf keinerlei Rücksicht nehmen muss.
Der Punkt ist doch, dass das korrektes C++ ist. Dem Aufrufer ist doch egal welches f() dann letztlich das Ergebnis liefert, er erwartet aber, dass es sinnvoll ist. Du als Entwickler hast aber keine Chance solch einen Aufruf mit deiner normalen überladenen Funktion abzufangen.
Man kann auch ein Beispiele konstruieren, in welchen dir solche Aufrufe natürlicher vorkommen bzw. unabdingbar sind:
template<class T1, class T2> T1 f( T2 myT2) { return T1(); } template<class T1, class T2> T1 f( T2 *myT2) { return T1(); } //... int i; int *pi1 = f<int*>(i); int *pi2 = f<int*>(&i);hustbaer schrieb:
BTW: bist du dir darüber im Klaren, dass bei deiner Variante hier das Template "template<typename T> void foo(T)" aufgerufen wird, und NICHT "template<typename T> void foo(T*)"?
Wo mit einer Spezialisierung reagiert werden kann, während einem ohne Spezialisierung der Fall durch die Finger rutscht.
hustbaer schrieb:
Und genau sowas halte ich für total kontraintuitiv.
Da gebe ich dir Recht, das hat aber in diesem Fall nichts mit Templatespezialisierung vs. Überladene Funktion zu tun. Da hätte man auch ohne Spezialisierung/Funktion intuitiv etwas anderes erwartet.
hustbaer schrieb:
1)...
2)...
3)...
4)...Ich würde sagen, man soll versuchen den Entwurf einfach zu halten, denn egal wie du es letztlich auch machst, wenn es komplizierter wird mit Überladung oder Spezialisierung, mehreren Parametern usw. wird dir kaum einer ad hoc intuitiv sagen können was für einen bestimmten Aufruf genau passiert.
hustbaer schrieb:
p.S.: noch ein lustiges Beispiel dafür, wieso Funktions-Template-Spezialisierungen böse sind:
Sind sie wirklich böse, oder hättest du es nur anders erwartet?
So, für mich war es das.
Ich finde wir sind sowieso schon zu lange auf dieser Nebensächlichkeit herumgeritten. Daher fällt es mir auch nicht schwer deine Meinung zu akzeptieren. Meinetwegen. Wenn also jemand in diesen Abwägungsprozess kommt kann er sich auch GRUNDSÄZTLICH für Überladung mit einer normalen Funktion entscheiden. Es ist ja kein Schuss ins Knie ... 