//template - explicit specialization in non-namespace scope
-
template< typename T > class MyClass { public: template< typename T2 > void foo() { std::cerr << " not implemented for that type " << std::endl; } template< > void foo<double>() { double x=0; std::cout << "int-foo" << std::endl; } };
MinGW (gcc)
explicit specialization in non-namespace scope `class MyClass<T>'| enclosing class templates are not explicitly specialized | `foo' is not a function template| invalid function declaration |
also wenn ich das richtig verstehe, dann darf ich die spezialisierung nicht innerhalb des Klassen-Scopes durchführen.
Aber wie schaut den die Spezialisierung hierfür für foo aus, wenn man sie ausserhalb durchführt?PS: cl von MSVC 2005/8 übersetzt das anstandslos...
template< typename T > class MyClass { public: template< typename T2 > void foo();// { std::cerr << " not implemented for that type " << std::endl; } }; template< typename T > template< typename T2 > void MyClass< T >::foo() { std::cerr << " not implemented for that type " << std::endl; } //wieso geht dann folgendes nicht? template< typename T > template< > void MyClass< T >::foo< double >() { double x=0; std::cout << "double-foo" << std::endl; }
ist es der grund, dass funktions-templates ausschließlich vollständig spezialisiert werden dürfen? und wenn ja, wie kann man das ausser mit einer "globalen" template funktion lösen?
-
Der Standardtrick(tm) ist ein minimales Template type2type und die gewöhnliche Überladung ausnutzen:
template <typename T> struct type2type { typedef T type; }; namespace { void foo (type2type<double> const&) { pups; } void foo (type2type<int> const&) {} void foo (...) { std::cerr << "Nicht implementiert" << std::endl; } } template <typename T> void foo () { foo (type2type<T> ()); }
Das „nicht-implementiert“ ist hier entweder das Nichtvorhandensein (Kompilierfehler) oder wie oben eine Ellipse (Laufzeitfehler).
Geht selbstverständlich alles auch im Klassennamensraum, da kannst du das, was hier im unbenannten Namensraum liegt auch einfach privatisieren.
-
super! 1000. Dank. genau das mit dem compiler-error galt es bei mir zu vermeiden, denn die klasse kann alle möglichen typen verarbeiten, nur die eine methode ist derzeit nur speziell für einen typen moeglich, andernfalls soll eine exception geworfen werden.
daher mach eich jetzt sowas wie:
template< typename T > class MyClass { public: template< typename T2 > T2 foo(const unsigned& arg1, const int& arg2, const type2type<double>& arg3) { std::cerr << " type = double " << std::endl; return T2(); } template< typename T2 > T2 foo(const unsigned& arg1, const int& arg2, const type2type<int>& arg3) { std::cerr << " type = int" << std::endl; return T2(); } template< typename T2 > T2 foo(const unsigned& arg1, const int& arg2, ... ); { std::cerr << " not implemented for that type " << std::endl; return T2(); } };
aber um das ursprungsproblem zu verstehen. meine erste lösung ging nicht, weil funktionstemplates nicht partiell spezialisiert werden dürfen? also im fall von "methoden-templates" von "template-klassen" muss man sowohl alle funktions typen sowie klassen-typen angeben, ist es das? wenn man das nich tmöchte/kann, dann verwendet man funktionsüberladung, ja?
PS: mir ist aufgefallen, dass der compile rnun meckert, wenn ich foo als erstes ein "int" und anstelle eines "unsigned int" übergebe. Liegt das an der überladung?
-
Du hast es erfasst: Methoden von Klassentemplates sind automatisch Funktionstemplates, Methodentemplates von Klassentemplates sind daher Funktionstemplates sowohl der Klassentemplate-Parameter als auch der Methodentemplate-Parameter und koennen nicht partiell spezialisiert werden. Allgemein sollte man bei der Spezialisierung von Funktionstemplates sowieso vorsichtig sein, da die Spezialisierungen nicht an der Overload-Resolution teilnehmen. Beispiel:
template <class T> void foo(T) {}; //template-foo void foo(float) {}; //ueberladung mit nontemplate template<> void foo<int>(int) {}; //spezialisierung von tempplate-foo, nimt nicht an OR teil! int main() { int i; foo(i); //ruft foo(float) auf!! }
hier wird -eventuell entgegen der Annahme des Programmierers - foo(float) aufgerufen. Der Grund: an der Overload Resolution nehmen nur template-foo und foo(float) teil. Da i in ein float konvertiert werden kann und ein non-template immer einem template vorgezogen wird, wird foo(float) aufgerufen, obwohl eine Spezialisierung fuer int vorhanden ist. Deshalb verzichtet filmors Trick17 auch voellig auf Spezialisierungen (auch bei freien Funktionen und in nicht-template-klassen)
-
pumuckl schrieb:
Du hast es erfasst: Methoden von Klassentemplates sind automatisch Funktionstemplates, Methodentemplates von Klassentemplates sind daher Funktionstemplates sowohl der Klassentemplate-Parameter als auch der Methodentemplate-Parameter und koennen nicht partiell spezialisiert werden. Allgemein sollte man bei der Spezialisierung von Funktionstemplates sowieso vorsichtig sein, da die Spezialisierungen nicht an der Overload-Resolution teilnehmen.
Membertemplates (egal ob Klassen- oder Funktionstemplates) können nur dann explizit spezialisiert werden, wenn alle umgebenden Templates (also die, von denen unser zu untersuchendes Template direkt oder indirekt Member ist) explizit spezialisiert ist. Der Standard führt dafür meines Wissens keine besondere Begründung an, allerdings kann man auch intelligent raten: ohne diese Beschränkung wäre es möglich, ein Template zu spezialisieren, dass (noch) nicht deklariert wurde - ein seltsames und in Hinblick auf die frühzeitige Überprüfung von Definitionen ungünstiges Verhalten. Zudem müsste man sich über die Auswirkungen auf die ODR Gedanken machen, denn eine explizite Spezialisierung ist kein Template.
template <class T> void foo(T) {}; //template-foo void foo(float) {}; //ueberladung mit nontemplate template<> void foo<int>(int) {}; //spezialisierung von tempplate-foo, nimt nicht an OR teil! int main() { int i; foo(i); //ruft foo(float) auf!! }
hier wird -eventuell entgegen der Annahme des Programmierers - foo(float) aufgerufen. Der Grund: an der Overload Resolution nehmen nur template-foo und foo(float) teil. Da i in ein float konvertiert werden kann und ein non-template immer einem template vorgezogen wird, wird foo(float) aufgerufen, obwohl eine Spezialisierung fuer int vorhanden ist.
Hier wird ohne Zweifel foo<int> aufgerufen werden. Es ist richtig, dass die Existenz einer expliziten Spezialisierung die Überladungsauflösung nicht beeinflusst. Das ist aber nur überraschend, wenn man gewissermaßen den falschen Standpunkt einnimmt. Jedenfalls ist hier die Spezialisierung foo<int> (unabhäng davon, dass dessen Definition keine Instanz eines Templates ist) ein "exact match" für das einzige Argument, während die notwendige Konvertierung dieses Arguments für void foo(float) "Promotion Rank" hat, das ist ungünstiger und folglich wird hier die Templatespezialisierung vorgezogen. Nicht-templatefunktionen werden nur dann vorgezogen, wenn sie für keinen Paramter zu einer ungünstigeren Konvertierungssequenz führen (im Kopf kann man das immer schnell überprüfen, wenn ein Parameter deduziert werden muss: der Konvertierungsrang ist hier beim Template immer "exact match", und damit entfallen Nichttemplatefunktionen häufig sehr schnell).
-
*gruebel* wie war denn dann das Beispiel wo eben nicht die Spezialisierung aufgerufen wurde? Sutter/Alexandrecu hatten da doch was in ihren C++ coding styles. Sry dass ich das falsch wiedergegeben hab
-
Ok, habs mal ausgebuddelt. Sutter nennts das Dimov/Abrahams Example in Exceptional C++ Style
template <class T> void f(T); //1 template <> void f<int*>(int*); //2, Spezialisierung von 1 template <class T> void f(T*); //3, Überladung von 1 /* ... */ int* pi; f(pi); //Ruft nicht wie evtl. erwartet Nr2 auf sondern Nr3!
Ich entschuldige mich ncohmal fuer das falsche Beispiel, nichts desto trotz lieber keine Funktionstemplates spezialisieren, sonst schreibt jemand anderes ne Überladung die besser passt und schon wird ne andere Funktion aufgerufen!
-
pumuckl schrieb:
Ok, habs mal ausgebuddelt. Sutter nennts das Dimov/Abrahams Example in Exceptional C++ Style
template <class T> void f(T); //1 template <> void f<int*>(int*); //2, Spezialisierung von 1 template <class T> void f(T*); //3, Überladung von 1 /* ... */ int* pi; f(pi); //Ruft nicht wie evtl. erwartet Nr2 auf sondern Nr3!
okay. und wenn ich es richtig verstanden habe, dann wird in diesem fall nicht die explizit spezialisierte nr 2 aufgerufen weil die template funktion nr einen höheren rank hat, wenn sie argument mäßig passt?
-
An der Ueberladungs-Aufloesung nehmen nur die Basis-Temlaptes teil, nicht die Spezialisierungen. In dem Fall also f(T) und f(T*) (Nr1 und 3). Und da passt das T* eher zum int* als das T. Das heisst, Nr 3 wird ausgewaehlt und benutzt, ungeachtet der Tatsache dass jemand f(T) genau fuer int* spezialisiert hat, bevor jemand f(T*) deklariert hat. Nach Spezialisierungen wird also erst Ausschau gehalten, wenn das Basistemplate bereits ausgewaehlt wurde, und da die Wahl des Compilers auf f(T*) faellt, schaut er sich Nr2 als Spezialisierung von f(T) garnicht erst an.
-
verwirrend, aber wenn man es erstmal weiß. somit sind funktions-spezialisierungen echt mit vorsivht zu genießen. danke!
-
pumuckl schrieb:
Ok, habs mal ausgebuddelt. Sutter nennts das Dimov/Abrahams Example in Exceptional C++ Style
template <class T> void f(T); //1 template <> void f<int*>(int*); //2, Spezialisierung von 1 template <class T> void f(T*); //3, Überladung von 1 /* ... */ int* pi; f(pi); //Ruft nicht wie evtl. erwartet Nr2 auf sondern Nr3!
Ich entschuldige mich ncohmal fuer das falsche Beispiel, nichts desto trotz lieber keine Funktionstemplates spezialisieren, sonst schreibt jemand anderes ne Überladung die besser passt und schon wird ne andere Funktion aufgerufen!
Bedenkenswert ist hierbei, was passiert, wenn wir die 2. und die 3. Deklaration miteinander vertauschen, also:
template <class T> void f(T); //1 template <class T> void f(T*); //2, Überladung von 1 template <> void f<int*>(int*); //3, Spezialisierung von 2
Damit ist plötzlich wieder alles richtig. Die Problematik entsteht ja dadurch, dass alle Spezialisierungen des 1. Templates, für die eine entsprechende Spezialisierung des 2. Templates existiert, durch jenes "verdeckt" werden, nicht unähnlich der Verdeckung von Namen. Im Unterschied zur Namensüberdeckung ist es allerdings nicht möglich, auf irgendeine andere Weise auf diese Spezialisierungen des 1. Templates nach der Deklaration des 2. Templates zu verweisen. Gäbe es partielle Spezialisierungen auch für Funktionstemplates, könnten wir für alle diese Fälle, die entsprechenden spezielleren Templates als partielle Spezialisierungen schreiben, und so die "Verdeckung" zuvor deklarierter expliziter Spezialisierungen vermeiden. So gesehen kann man sich informell die Überladung des spezielleren Templates auch als partielle Spezialisierung vorstellen, die zusätzlich alle zuvor deklarierten expliziten Spezialisierungen vernichtet, soweit sie in ihre Domäne fallen..
Die angesprochende Problematik ensteht also dann und nur dann, wenn
1. mehrere Funktionstemplates überladen werden, und
2. wenigstens ein Funktionstemplate spezieller als anderes überladenes Template ist, und
3. explizite Spezialisierungen des allgemeineren Templates deklariert werden, für die auch eine Spezialisierung des spezielleren Templates existiert (bevor jenes deklariert wurde, danach geht das ja nicht mehr)Soweit ein Überladungset durch den Anwender nicht frei erweitert werden kann, ist somit nichts gegen explizite Spezialisierungen einzuwenden, indem man ein paar Grundregeln einhält (z.B. explizite Spezialisierungen erst deklarieren nachdem alle Templates deklariert wurden). Oder man verbietet weitere Überladungen (wie es z.B. Namensraum std der Fall ist - unser Problem mag mit ein Grund für diese Entscheidung sein, sie wird aber wohl in C++0x revidiert werden). Oder man verbietet eben explizite Spezialisierungen. Oder man begrenzt per SFINAE die Domäne jeder Überladung auf den minimal notwendigen Bereich (so dass dann keine Überschneidungen existieren) - das kann noch aus anderen Gründen sinnvoll sein.
Das Verzicht auf Spezialisierungen ist also eine mögliche Lösung, keineswegs aber die Einzige. Sie als solche hinzustellen, schießt also wie einige andere Regeln deutlich übers Ziel hinaus.
-
Ich glaube ich muss dir zum ersten Mal widersprechen, camper.
f<int*>(int*) ist und bleibt eine Spezialisierung von f(T). Die entsprechende Spezialisierung von f(T*) die du meintest, hieße f<int>(int*)
Die Problematik entsteht nicht wirklich durch die gegenseitige Überdeckung der template-spezialisierungen sondern durch die Überdeckung bei der Herleitung der Templateparameter durch die Funktionsargumente. Diese Überdeckung kann man auch ähnlich wie die Namensüberdeckung wieder auflösen. bei letzterer, indem man explizit einen qualifizierten namen angibt, bei den Funktionstemplates, indem man den templateparameter explizit mit angibt:template <class T> void f(T); //1 template <class T> void f(T*); //2, Überladung von 1 template <> void f<int*>(int*); //3, Spezialisierung von 1!! template <> void f<int>(int*); //4, Spezialisierung von 2 void f(int*); //5, nontemplate-überladung von 1 und 2 /* ... */ int* pi; f(pi); //ruft 5 auf f<int>(pi); //ruft 4 auf f<int*>(pi); //ruft 3 auf
Ich hoffe das ist so richtig und es gibt nicht irgendwelche ambiguities die ich übersehen hab, aber eigentlich müsste der Compiler das auflösen können, oder?
Dass Verzicht auf Spezialisierungen nicht die einzig mögliche Lösung ist, ist mir klar, deshalb habe ich weiter oben auch nicht gesagt, dass man Spezialisierungen vermeiden sollte (auch wenn Sutter und Alexandrescu das in ihren Coding Standards so als Richtlinie Schreiben), sondern dass man damit vorsichtig umgehen sollte. Eine Möglichkeit, von der ich mal gelesen habe, ist z.B., ein struct mit einer öffentlichen Methode zu schreiben und den Aufruf an dieses struct weiterzuleiten, das durchaus spezialisiert werden kann, sogar partiell:
template<class T> struct foo { static void do(T); }; template <class T> void f(T t) { //nicht spezialisieren, statt dessen foo spezialisieren foo<T>::f(t); }
das ist natürlich mit einem gewissen Aufwand verbunden, der nicht immer Not tut. Es ist eben auch nur eine Möglichkeit unter vielen.
-
Gut aufgepasst. Es wäre schön, wenn das öfter passieren würde, meine Beiträge enthalten im allgemeinen erheblich mehr Fehler, als die Menge der Widersprüche erwarten lässt... Ein Teil der Argumentation lässt sich möglicherweise retten, aber es scheint ja sowieso kein Diskussionsbedarf mehr zu bestehen.
-
doch, aber ich vermute den meisten ist das schon zu spezifisch. ich sitze bloß mit staunenden augen vor meinen drei monitoren
danke aber für den ausführlichen exkurs!