Template Spezialisierung für Memberfunction
-
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.
-
Nexus schrieb:
Zur Default-Konstruierbarkeit habe ich oben noch etwas gesagt. Hängt halt davon ab, wie generisch dein Code sein soll.
Stimmt, grad gelesen. Ich würde es gerne nicht als Bedingung angeben, aber gibt es in C++ Templates solche Constraints wie C# Generics? Ich weiß es grad nicht.
Wobei mir grad einfällt, ein Defaultctor wäre schon ne feine Sache und als zweite Bedingung nen Überladenen >> Operator wäre auch ganz fein, aber wie gesagt, wie kann ich das definieren?
-
Firefighter schrieb:
Wobei mir grad einfällt, ein Defaultctor wäre schon ne feine Sache und als zweite Bedingung nen Überladenen >> Operator wäre auch ganz fein, aber wie gesagt, wie kann ich das definieren?
dokumentiers. wenn die Operationen fehlen, wird der Compiler schon meckern. Mehr geht nicht.
-
Firefighter schrieb:
Stimmt, grad gelesen. Ich würde es gerne nicht als Bedingung angeben, aber gibt es in C++ Templates solche Constraints wie C# Generics?
Keine Ahnung wie das in C# genau aussieht, aber du hast in C++ Type-Traits. Concepts waren auch mal eine Diskussion, aber die kommen vorerst noch nicht in die Sprache.
Firefighter schrieb:
Wobei mir grad einfällt, ein Defaultctor wäre schon ne feine Sache und als zweite Bedingung nen Überladenen >> Operator wäre auch ganz fein, aber wie gesagt, wie kann ich das definieren?
Du musst das gar nicht explizit definieren. Code, der die Anforderungen nicht erfüllt, kompiliert nicht.
-
Nexus schrieb:
Du musst das gar nicht explizit definieren. Code, der die Anforderungen nicht erfüllt, kompiliert nicht.
Vollkommen korrekt, ganz vergessen. Gut dann nehm ich das Type2Type struct jetzt raus und lass T als Parameter bei der readImpl.
-
Wenn du eh nur einen generischen Fall hast, kannst du
readImpl()
auch gleich rausschmeissen und direktread()
verwenden.
-
Joa schon, ok ich dank euch fürs erste.
-
Wollte nochmal die ganze bisherige Lösung präsentieren.
Um ehrlich zu sein gefällt die mir gar nicht. Fühl mich nich gut beim anschauen der LösungHabt ihr noch anmerkungen?
class Input { private: Input(){} template<class T> static void getInput(T& object) { std::ios_base::iostate state; while(!(std::cin>>object)) { std::cout<<"Wrong format, please try again!"<<std::endl; std::cin.clear(); std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); } } public: template<class T> static T read() { T object; getInput(object); return object; } static int read(int lowerBound, int upperBound) { if(lowerBound > upperBound) std::swap(lowerBound,upperBound); int object; getInput(object); while(object < lowerBound || object > upperBound) { std::cout<<"The value does fall not in the expected range!("<<lowerBound<<"-"<<upperBound<<")"<<std::endl; getInput(object); } return object; } };
-
Ist eigentlich soweit in ordnung. die state variable kannste entfernen, benutzt du ja nirgendwo. Ich würde das nicht static machen wollen und das Ganze schreit eigentlich danach, daraus freie Funktionen zu machen bzw aus der class Input einen namespace Input zu machen. Wir sind ja nicht in Java :).
du kannst dir auch überlegen, das zweite Read generischer zu machen, indem du statt int einen beliebigen Typen erlaubst. so kannst du die Funktionen direkt für float, double etc verwenden.
-
Was mir gerade aufgefallen ist:
- Deine Klasse hat keinen Status und nur statische Methoden (abgesehen vom sinnlosen Konstruktor). Du könntest auch einen Namensraum benutzen, allerdings müsstest du dann die privaten Member in einem
detail
-Namespace verstecken. - Die Variable
state
wird nicht benutzt. - Gibt es einen Grund für das Tauschen der Bounds? Wäre es nicht sinnvoller, richtig übergebene Bounds zu fordern und mit
assert
sicherzustellen? Ich habe grundsätzlich eine gewisse Abneigung gegen Versuche, Logikfehler gerade zu biegen - Warum
while(object < lowerBound || object > upperBound)
? Wird diese Bedingung jemalstrue
, falls der erstegetInput(object)
-Aufruf fehlschlägt? Davon abgesehen entspricht die Struktur eherdo-while
.
- Deine Klasse hat keinen Status und nur statische Methoden (abgesehen vom sinnlosen Konstruktor). Du könntest auch einen Namensraum benutzen, allerdings müsstest du dann die privaten Member in einem
-
Man dankt für die Kritik und wird sie umsetzen
Das mit dem Namespace klingt gut, werde ich glaube umsetzen.