Template Spezialisierung für Memberfunction
-
Hallo Leute,
vor folgendem Problem steh ich aktuell. Ich habe eine nicht-template Klasse, möchte aber eine Methode anbieten welche über templates "definiert" werden kann.
Diese template-Member Funktionen möchte ich jedoch noch spezialisieren, aber ich bin mir gerade über die korrektheit nicht wirklich sicher und wollte mal um Rat fragen.Hier mal der Code:
class Input { private: Input(){} public: template<class T> static T read(); template<> static int read(); };
Muss ich wirklich für jede spezialiserte Methode eine Extra Klasse ála
class Input { private: Input(){} public: template<class T> static T read(); };
und
class Input { private: Input(){} public: template<> static int read(); };
erstellen?
Und wie müsste dann die implementations Datei dazu aussehen? Kann ich da einfach die jeweiligen Spezialisierungen "aufzählen" und implementieren?
-
Habs selbst rausgefunden. Ich spezialisiere das nich in der .h Datei sondern im Implementationsfile. Ist doch korrekt oder?
-
1. templates und *.cpp vertragne sich nicht
2. du kannst keine Memberfunktionen spezialisieren
3. tu das:class Input { private: //generische implementation template<class T> static void readImpl(T& obj){...} //überladung rockt! static void readImpl(int& obj){} public: template<class T> static T read(){ T obj; readImpl(obj); return obj; } };
-
Das sieht geil aus. Dank dir. Aber statische Funktionen lassen sich anscheind spezialiseren oder? Zumindest haute es bei mir hin.
-
jo, habs gerade nachgelesen. es funktioniert, aber nur ausserhalb der Klasse. innerhalb der Klasse kannst du nix spezialisieren. Trotzdem ist template spezialisierung doof und sollte vermieden werden.
-
Ok, kannst du mir erklären warum es doof ist und vermieden werden sollte? Vielleicht hast du ja einen Link oder so.
-
-
otze schrieb:
//generische implementation template<class T> static void readImpl(T& obj){...} //überladung rockt! static void readImpl(int& obj){}
Überladung rockt tatsächlich, aber für die Referenz muss man unnötigerweise extra ein Objekt erstellen. Einerseits hat man so mehr Code, andererseits kann das je nach
T
teuer bis unmöglich sein (z.B. weil man an der Stelle des Aufrufs nicht genügend Informationen für eine sinnvolle Konstruktion hat).Darum, Type-Wrapper verwenden.
template <typename T> struct Type2Type {}; template <typename T> static T readImpl(Type2Type<T>); static int readImpl(Type2Type<int>);
Firefighter schrieb:
Ok, kannst du mir erklären warum es doof ist und vermieden werden sollte? Vielleicht hast du ja einen Link oder so.
Zu viele Einschränkungen, Sonderregeln und Unintuitivitäten. Wurde hier schon ein paar Mal diskutiert, finde gerade nichts. Im Prinzip ist Überladung vorzuziehen, es bietet auch mehr Möglichkeiten als Funktionstemplate-Spezialisierung.
-
Den Type-Wrapper versteh ich jetzt nicht. Nen leeres struct? Oder hast du das nur zu Demozwecken gemacht? Wieso brauch ich den an der Stelle?
Edit
Erreichen will ich ja eigentlich nur sowas:
int i = Input::read<int>(); double i2 = Input::read<double>(); std::string i3 = Input::read<std::string>(); // usw.
-
Firefighter schrieb:
Den Type-Wrapper versteh ich jetzt nicht. Nen leeres struct? Oder hast du das nur zu Demozwecken gemacht?
Nein, das ist alles. Simpel, aber wahnsinnig effektiv – auf einen Schlag verschwindet die Nachfrage nach Funktionstemplate-Spezialisierungen.
Der Aufruf sieht so aus:
int i = Input::read(Type2Type<int>()); double i2 = Input::read(Type2Type<double>()); std::string i3 = Input::read(Type2Type<std::string>());
Damit du die überaus komplizierte Implementierung nicht selbst schreiben musst, gibts das schon fertig als
boost::type
-
Firefighter schrieb:
Den Type-Wrapper versteh ich jetzt nicht. Nen leeres struct? Oder hast du das nur zu Demozwecken gemacht? Wieso brauch ich den an der Stelle?
Hi,
in meiner Version gab es das Problem, dass T einen default-ctor braucht. Das ist vielleicht nicht immer gegeben. Also braucht man einen anderen Mechanismus um die Überladung zu triggern. In dem Fall erzeugst du einen Typ, der eine 1:1 zuordnung zu dem ursprungstyp darstlelt, abe rnicht die Limitierung des konstruktors hat um damit die Funktion zu wählen.
ich vervollständige mal:
template <typename T> struct Type2Type {}; struct MyType{ MyType(int);//nicht default construierbar }; template <typename T> static T readImpl(Type2Type<T>){ T obj; //...tu was return obj; } static int readImpl(Type2Type<int>){ int i; //...tu was return i; } static MyType readImpl(Type2Type<MyType>){ MyType obj(5);//okay, ctor mit 1 argument aufgerufen //..tu was return obj; } template<class T> static T read(){ return readImpl(Type2Type<T>()); } //später MyType obj = Input::read<MyType>();//Fehler mit meiner Version, hier okay!
-
Ja ick weiß das es da etwas von boost gibt. Leider dürfen wir keine Fremdbibliotheken im Studium benutzen und sollen es selber schreiben. Hatte da mit dem Dozenten auch schon diverse Diskussionen aber er will das wir alleine drauf kommen
Auch wenn ich die Lösng mit dem Wrapper gut finde. Aber der aufruf gefällt mir irgendwie gar nicht. Ich wollte das schön herrlich über den Template-Parameter erschlagen. Ist das nicht irgendwie möglich?
-
Firefighter schrieb:
Ja ick weiß das es da etwas von boost gibt. Leider dürfen wir keine Fremdbibliotheken im Studium benutzen und sollen es selber schreiben.
Tja, dann bleibt dir nur die überaus komplizierte Implementierung.
Firefighter schrieb:
Auch wenn ich die Lösng mit dem Wrapper gut finde. Aber der aufruf gefällt mir irgendwie gar nicht. Ich wollte das schön herrlich über den Template-Parameter erschlagen. Ist das nicht irgendwie möglich?
War zu erwarten. Dann tu das eben umleiten.
public: template <typename T> static T read() { return readImpl(Type2Type<T>()); } private: template <typename T> static T readImpl(Type2Type<T>); static int readImpl(Type2Type<int>);
-
Die Frage die ich mir gerade stelle ist folgende: Brauch ich überhaupt die Überladungen, oder kann ich es nicht einfach so hier machen:
class Input { private: Input(){} template<class T> static T readImplementation(Type2Type<T>) { std::ios_base::iostate state; T object; while(state) { std::cin>>object; state = std::cin.rdstate(); if(state) { std::cout<<"Wrong format, please try again!"<<std::endl; std::cin.clear(); std::cin.get(); } } return object; }; public: template<class T> static T read() { return readImplementation(Type2Type<T>()); } };
Sonst müsste ich ja jetzt für int und den rest jedesmal die selbe Methode schreiben nur das ich die Typen in den Methoden ersetze. Die Abfragen und alles müssen ja trotzdem rein oder nicht?
-
ja, ich habe angenommen, dass die default implementation (so ähnlich) aussieht und dass du Probleme mit einigen wenigen Typen hast, die anders behandelt werden müssen. Immerhin haste ja explizit nach ner Spezialisierung gefragt, für solche Ausnahmefälle sind die da :). Wnen du keine Ausnahmen hast, dann kannste das alles direkt in read schreiben und brauchst den Umweg nicht.
ansonsten glaube ich, dass die Implementation nicht ganz richtig ist, solltest du vielleicht nochmal testen
-
Hehe. Ja ich habe tatsächlich noch Probleme, wenn man einen Int möchte und dann "asd" eingibt, erhalte ich 3mal die Ausgabe das ich was falsches eingegeben habe, das muss noch gefixed werden.
Ich sag mal so, in unseren Standardprogrammen werden wir in der Uni sicherlich nur int,double,char und std::string abfragen. Wenn jemand jetzt aber für seine Klasse den operator>> überlädt könnte er dafür ja auch meine Funktion nutzen und diese Spezialfälle kann ich jetzt nich abdecken. Aber für die "Standard-Typen" sollte es ja erstmal aussreichen oder nicht?
-
jap
-
Firefighter schrieb:
Wenn jemand jetzt aber für seine Klasse den operator>> überlädt könnte er dafür ja auch meine Funktion nutzen und diese Spezialfälle kann ich jetzt nich abdecken.
Warum nicht? Ich dachte, dein Code sollte generisch sein, immerhin verwendet er Templates.
Natürlich könntest du die Generizität noch erhöhen, wenn du Default-Konstruierbarkeit und Kopierbarkeit nicht als Bedingungen festlegen würdest.
-
Ok, dann hab ich noch eine letzte Frage.
Wir haben ja vorhin den extra Typen "Type2Type" eingefügt um das Problem mit den eventuell nicht konstruierbaren Typen zu umgehen.
Den brauch ich ja rein technisch gesehen jetzt nicht mehr? Weil ich ja jetzt in der read-Methode eh wieder
T object
mache und mir das Problem so wieder ins Haus hole oder nicht?
-
Firefighter schrieb:
Wir haben ja vorhin den extra Typen "Type2Type" eingefügt um das Problem mit den eventuell nicht konstruierbaren Typen zu umgehen.
Ja, aber auch um von Funktionstemplate-Spezialisierungen wegzukommen. Wenn du keine Spezialisierungen hast, kannst du auch ein ganz normales Template verwenden.
Zur Default-Konstruierbarkeit habe ich oben noch etwas gesagt. Hängt halt davon ab, wie generisch dein Code sein soll.