Template Methode nur spezialisiert vererben
-
nabend zusammen,
geht sowas?
class Base { public: template<typename T> virtual void foo(T const&) = 0; }; class Derive : public Base { template<> void foo(std::string const& s) { /* machwas */ } } int main() { Derive d; std::string s = "Test"; d.foo<std::string>(s); // soll funktionieren! std::vector<int> v(1000, 5); d.foo<std::vector<int>>(v); // soll nicht funktionieren! return 0x0; }sprich man garantiert per abstrakter klasse eine methode die ein template ist, definiert in der abgeleiteten klasse aber nur eine spezialisierung dieser template methode, damit quasi nicht erlaubt wird, dass andere template spezifikationen für diese klasse aufgerufen werden könne?
-
es gibt keine virtuellen Templates, damit ist die Frage hinfällig.
-
otze schrieb:
es gibt keine virtuellen Templates, damit ist die Frage hinfällig.
ohne spass nicht?? O_o
schade so wie ichs mir gedacht hatte gehts ja dann wohl nicht -.-'
-
Jop. Gibts nicht.
-
Ich vermute, dass der Frage ein grundsätzliches (und weit verbreitetes) Missverständnis über die Natur von Templates zugrunde liegt.
Es handelt sich bei foo in deinem Beispiel nicht um eine Templatemethode, und es handelt sich bei (zum Beispiel) std::vector nicht um eine Templateklasse. Es handelt sich um eine Methodentemplate und eine Klassentemplate respektive, oder auf deutsch um eine Methodenvorlage und eine Klassenvorlage.
Templates als solche sind keine Objekte in irgendeiner Form. Eine Klassenvorlage ist keine Klasse, eine Funktionsvorlage ist keine Funktion, und Vorlagen allgemein sind zur Laufzeit überhaupt nicht mehr vorhanden - sie werden allein zur Compilezeit vom Compiler benutzt, um Klassen bzw. Funktionen zu erzeugen. Du kannst beispielsweise keine Zeiger auf Templates nehmen.
Das ist dann auch das Problem mit virtuellen Methodenvorlagen - wenn man keinen Zeiger auf etwas nehmen kann, kann man diesen Zeiger auch nicht in die virtuelle Funktionstabelle schreiben.
In diesem Punkt unterscheiden sich (wenn ich mich jetzt nicht böse vertue) Templates aus C++ stark von Generics, wie man sie in Java oder .net kennt - dort läuft das ganze über Reflection-Mechanismen, die in C++ nicht vorhanden sind. Wenn man eine böse Zunge hat, könnte man sagen, dass dort zur Laufzeit nach Bedarf hin- und hergecastet wird.
-
Also wenn du auch ohne virtual auskommst dann wäre das doch eine einfach Lösung:
class Base { public: template<typename T> void foo(T const&) { //falls du hier was machen willst... } }; class Derive : public Base { public: //template<> void foo(std::string const& s) { /* machwas */ } //void foo(std::string const&) versteckt alle Überladungen von foo (d.h. //jedes template aus Base)! } int main() { Derive d; std::string s = "Test"; d.foo<std::string>(s); // funktioniert! std::vector<int> v(1000, 5); d.foo<std::vector<int>>(v); // funktioniert nicht! //ABER Base *b = &d; b->foo( v ); //funktioniert, ruft Base::foo<...> auf b->foo( s ); //funktioniert, ruft Base::foo<...> auf (weil nicht virtuell) return 0x0; }Ich finds auch bescheuert, dass virtual member template functions nicht erlaubt sind... Fühlt sich irgendwie an als wären die Compilerbauer zu faul, das umzusetzen (auch wenn ich ihre Arbeit ansonsten sehr schätze).
@Seldon: Warum sollte es nicht möglich sein, für jede Instanzierung der template function einen neuen vtable zu erstellen?
-
GorbGorb schrieb:
@Seldon: Warum sollte es nicht möglich sein, für jede Instanzierung der template function einen neuen vtable zu erstellen?
Vermutlich aus dem selben Grund, warum du Templates nicht eigenständig übersetzen kannst - der Compiler weiß gar nicht, welche Instanzen es überhaupt gibt (oder irgendwann geben wird).
-
GorbGorb schrieb:
Ich finds auch bescheuert, dass virtual member template functions nicht erlaubt sind... Fühlt sich irgendwie an als wären die Compilerbauer zu faul, das umzusetzen (auch wenn ich ihre Arbeit ansonsten sehr schätze).
Bevor du mit solchen Vermutungen auftrittst: Überleg dir, was virtuelles Template überhaupt bedeutet. Besonders wie eine entsprechende VTable dann aussehen sollte. Bedenke hierbei dass Templates komplett zur Kompilierzeit und virtuelle Methodenaufrufe komplett zur Laufzeit aufgelöst werden.
-
GorbGorb schrieb:
Also wenn du auch ohne virtual auskommst dann wäre das doch eine einfach Lösung:
class Base { public: template<typename T> void foo(T const&) { //falls du hier was machen willst... } }; class Derive : public Base { public: //template<> void foo(std::string const& s) { /* machwas */ } //void foo(std::string const&) versteckt alle Überladungen von foo (d.h. //jedes template aus Base)! } int main() { Derive d; std::string s = "Test"; d.foo<std::string>(s); // funktioniert! std::vector<int> v(1000, 5); d.foo<std::vector<int>>(v); // funktioniert nicht! //ABER Base *b = &d; b->foo( v ); //funktioniert, ruft Base::foo<...> auf b->foo( s ); //funktioniert, ruft Base::foo<...> auf (weil nicht virtuell) return 0x0; }und genau da liegt der knackpunkt: ich hab halt nur den zeiger auf die basisklasse...
-
Skym0sh0, was willst du genau erreichen (nicht wie)?
-
Nexus schrieb:
Bevor du mit solchen Vermutungen auftrittst: Überleg dir, was virtuelles Template überhaupt bedeutet. Besonders wie eine entsprechende VTable dann aussehen sollte. Bedenke hierbei dass Templates komplett zur Kompilierzeit und virtuelle Methodenaufrufe komplett zur Laufzeit aufgelöst werden.
Ja natürlich, wenn templates komplett zur Compilezeit aufgelöst werden, dann wüsste man zu diesem Zeitpunkt ja, wie viele verschiedene template functions es gibt.
Und ob eine Klasse jetzt 10 "normale" virtual Functions hat, oder 10x eine template Function mit unterschiedlichen Parametern sollte ja völlig egal sein.
Ich denke der Grund, warum das nicht implementiert ist, ist, dass man die ganze Klasse neu compilen müsste (und alles, was davon abhängt), wenn später eine neue Spezialisierung der template function benötigt wird. Genau dagegen hätte ich ja gar nichts, und deshalb nervt es mich ein bisschen, dass man hier auf irgendwas weniger effektives zurückgreifen muss.
Wenn man sich das Neukompilieren sparen wöllte könnte man ja auch freiwillig auf solche Konstrukte verzichten, so hat man keine Wahl.
-
Angenommen, ich hätte so etwas. Ich baue mir eine Bibliothek zusammen, in der sämtliche Konkretisierungen der Methodenvorlage in einer virtuellen Funktionstabelle enthalten sind, die ich beim Bauen der Bibliothek erstellt habe.
Dann kommt der Benutzer der Bibliothek und konkretisiert die Methodenvorlage mit einem seiner eigenen Typen. Wie kriege ich die in die Funktionstabelle der betroffenen Klasse und aller ihrer Kindklassen? Bedenke, dass ich womöglich gar nicht alle Kindklassen kenne.
Ferner: Der Benutzer kommt her und leitet von einer der abgeleiteten Klasse eine weitere Klasse ab, in der er die virtuelle Methodenvorlage mit einem seiner eigenen Typen spezialisiert. Wie kriege ich die neue Konkretisierung in die virtuelle Funktionstabelle der Basisklasse, ihrer Basisklasse und aller Klassen, die direkt oder indirekt von einer dieser Klassen erben?
Angenommen ferner, dieser Benutzer schreibt ein Plugin für eine Anwendung, die meine Bibliothek benutzt, und er weiß nicht, welche Konkretisierungen diese Anwendung bereits eingeführt hat oder in neueren Versionen einführen wird. Er darf die Reihenfolge der Funktionen in den virtuellen Funktionstabellen nicht verändern, damit die Anwendung nicht zusammenbricht. Er muss außerdem wissen, wo seine neu eingeführten Konkretisierungen zur Laufzeit liegen werden, und als wäre das nicht genug, muss er darauf achten, sich nicht mit anderen Plugins um Plätze in den virtuellen Funktionstabellen zu prügeln.
Aber damit ist es ja noch nicht vorbei: Wenn die Anwendung oder andere Plugins Klassen von der Basisklasse oder einer ihrer Kindklassen ableiten, muss er sie finden und deren virtuelle Funktionstabellen um die neue Konkretisierung erweitern. Selbst, wenn diese keine externe Linkage haben.
Ich hab jetzt nicht mitgezählt, wie viel davon völlig unmöglich ist und wieviel nur jenseits jeder Praktikabilität liegt, aber es ist mir durchaus verständlich, warum das Standardisierungskomitee sich gehütet hat, derart hanebüchene Anforderungen zu stellen.
-
seldon schrieb:
Angenommen, ich hätte so etwas. Ich baue mir eine Bibliothek zusammen, in der sämtliche Konkretisierungen der Methodenvorlage in einer virtuellen Funktionstabelle enthalten sind, die ich beim Bauen der Bibliothek erstellt habe.
Dann kommt der Benutzer der Bibliothek und konkretisiert die Methodenvorlage mit einem seiner eigenen Typen. Wie kriege ich die in die Funktionstabelle der betroffenen Klasse und aller ihrer Kindklassen? Bedenke, dass ich womöglich gar nicht alle Kindklassen kenne.
Ferner: Der Benutzer kommt her und leitet von einer der abgeleiteten Klasse eine weitere Klasse ab, in der er die virtuelle Methodenvorlage mit einem seiner eigenen Typen spezialisiert. Wie kriege ich die neue Konkretisierung in die virtuelle Funktionstabelle der Basisklasse, ihrer Basisklasse und aller Klassen, die direkt oder indirekt von einer dieser Klassen erben?
Angenommen ferner, dieser Benutzer schreibt ein Plugin für eine Anwendung, die meine Bibliothek benutzt, und er weiß nicht, welche Konkretisierungen diese Anwendung bereits eingeführt hat oder in neueren Versionen einführen wird. Er darf die Reihenfolge der Funktionen in den virtuellen Funktionstabellen nicht verändern, damit die Anwendung nicht zusammenbricht. Er muss außerdem wissen, wo seine neu eingeführten Konkretisierungen zur Laufzeit liegen werden, und als wäre das nicht genug, muss er darauf achten, sich nicht mit anderen Plugins um Plätze in den virtuellen Funktionstabellen zu prügeln.
Aber damit ist es ja noch nicht vorbei: Wenn die Anwendung oder andere Plugins Klassen von der Basisklasse oder einer ihrer Kindklassen ableiten, muss er sie finden und deren virtuelle Funktionstabellen um die neue Konkretisierung erweitern. Selbst, wenn diese keine externe Linkage haben.
Ich hab jetzt nicht mitgezählt, wie viel davon völlig unmöglich ist und wieviel nur jenseits jeder Praktikabilität liegt, aber es ist mir durchaus verständlich, warum das Standardisierungskomitee sich gehütet hat, derart hanebüchene Anforderungen zu stellen.
In die vbl an Stelle bei diesem Namen eine Funktion bauen, die für alle Template-Instanzen dieses Namens da ist. Sie matcht zur Laufzeit anhand von typeid über alle Parametertypen und ist fast so "schnell" und "sinnvoll" wie dynamic_cast. Das Problem ist bis dahin noch weniger technischer Natur als mehrfach sittlicher Natur, fürchte ich.
-
Tut mir Leid, das war zu abstrakt für mich. Kannst du konkret erklären, warum folgendes nicht möglich sein soll?
Zustand 1:
class Base { public: template< class Type > virtual void func( Type ) { } }; class A : public Base { public: void func( int ) { // überschreibt void Base::func<int>(int) } }; class B : public Base { public: void func( double ) { // überschreibt void Base::func<double>(double) } };Es ist offensichtlich wie das übersetzt werden könnte: Es gibt 2 virtuelle Funktionen in Base, die zwar jeweils zum gleichen Template gehören, aber sich ansonsten ja verhalten, als gäbe es 2 Funktionen func_int und func_double.
Wenn jetzt ein User daherkommt und sowas macht wieclass C : public Base { public: void func( short ) { } };bedeutet das, dass es jetzt 3 Funktionen gibt, nämlich func_int, func_double und func_short. Wenn es nicht möglich ist, unabhängig für func_short einen eigenen vtable zu erstellen, dann können immernoch Base, A und B mit C zusammen neu übersetzt werden. Mit pure virtual functions, da gebe ich euch ja Recht, wäre das ganze etwas komplizierter. Ich bin mir zwar ziemlich sicher, dass es möglich ist, auch so etwas sinnvoll zu implementieren, aber jetzt ist es mir zu spät für solche Gedankenspiele. Notfalls könnte man immernoch allein den Gebrauch von pure virtual template functions verbieten.
-
GorbGorb schrieb:
Mit pure virtual functions, da gebe ich euch ja Recht, wäre das ganze etwas komplizierter.
Nee, einfacher.
class Base { public: template< class Type > virtual void func( Type ) { cout<< boost::something_happens<T>::value; } };ist schwieriger, ja unmöglicg, ohne den ganzen Quellcode den Erben zu geben als
class Base { public: template< class Type > virtual void func( Type ) = 0; };Ich denke da zum Beispiel an
class A: public Base { template<> /*virtual*/ void func<double>(double d) { cout<<"ima double\n"; Base::func(d); } };Andererseits, warum nicht den ganzen Quellcode den Erben geben? Tut man bei Templates ja eh.
-
@Volkard: Diese Funktion müsste allerdings alle Konkretisierungen kennen, um den Dispatch durchführen zu können.
@GorbGorb: Mal abgesehen davon, dass man, selbst wenn man den gesamten Code hat, das gesamte Kompilationsmodell von C++ über den Haufen werfen müsste, um es so zu machen, wie du es erwartest, ist es stumpf nicht so, dass man zur Compilezeit den gesamten Code, den man benutzt, kennt - wir haben alle schon mit Bibliotheken von Dritten gearbeitet. Wie soll der Compiler (bzw. Linker) aus einer kompilierten DLL die virtuelle Funktionstabelle einer Klasse
namespace { // <-- das bedeutet, die Klasse ist nach außen nicht sichtbar. class Foo : public Base { // hier vielleicht nicht mal eine Überladung, sondern nur ein paar // zusätzliche Funktionen }; }herausfischen und anpassen, wenn das Hauptprogramm eine neue Konkretisierung hinzufügt? Den eigentlichen Code hat man ja dann nicht mehr.
Dazu: Selbst wenn man es im Compiler lösen könnte, wäre das nicht genug - der Laufzeitlinker ist gefragt. Wenn ich aus einer Bibliothek eine FUnktion
Base *make_foo();exportiere, die per dlsym oder GetProcAddress zur Laufzeit dynamisch gelinkt werden soll und Objekte einer nur der DLL in allen Details bekannten, von Base abgeleiteten Klasse zurückgibt, in Abwesenheit eines Compilers die gesamte Hierarchie upgedatet werden? Und sie müsste upgedatet werden. Der Standard kann Callback-Mechanismen nicht implizit verbieten.
Was, wenn man mehr als eine Bibliothek linken will? Dann müssen nicht nur die virtuellen Funktionstabellen angepasst werden, sondern auch alle Stellen im Code, die sie benutzen. Wenn Bibliothek 1 eine Funktion
void do_something_with(Base &);exportiert und Bibliothek 2 dafür sorgt, dass die Reihenfolge der Funktionen in den virtuellen Funktionstabellen sich ändert, ruft die Funktion auf einmal die falsche Methode auf. Wenn Bibliothek 1 bevorzugte Behandlung anmeldet und nur die Überladungen aus Bibliothek 2 verschoben werden, hast du das selbe Problem mit entsprechenden Funktionen in Bibliothek 2.
Auch hier muss man darauf vorbereitet sein, dass eine Bibliothek zur Laufzeit dynamisch gelinkt wird. Der Compiler soll sich damit befassen? Ein Compiler ist nirgends in Sicht.
Ich glaube nicht, dass es auf der Welt einen Laufzeitlinker gibt, der solche Mechanismen beherrscht. Ich glaube auch nicht, dass es jemals einen geben wird.
-
Nexus schrieb:
Skym0sh0, was willst du genau erreichen (nicht wie)?
ich habe eine basis klasse die eine init methode(wehe es kommt mir einer mit RAII, da kann ich aus anderen gründen nicht nutzen - die objekte sind alle erstellt, werden aber nur amnchmal benötigt) anbietet und die einen parameter erwartet (besser sage ich erstmal, erwarten könnte - bin mir nicht sicher ob ich das so in der art lösen soll oder besser ganz anders). jetzt braucht aber deriveA einen String als parameter während DeriveB einen vector<float> braucht und deriveC braucht FooBar... usw.
das ding ist, ich greife über zeiger auf die basisklasse auf die objekte zu. wo ich die init methode aufrufe lässt sich eine fallunterscheidung einbauen (wobei ich merke grad das das alles nicht so toll wäre und die polymorphie aushebeln würde...)
ich muss dazu sagen, mir kam diese idee mit den virtuellen template methoden nur mal kurz am rande ohne weiter drüber nachgedacht zu haben, ich hab also kein konzept die dann auch so schon zu benutzen. mein ziel bei diesem lernprojekt ist, sauberen code zu schreiben (ich führe mir deshalb auch derzeitig einschlägige scott meyers lektüre zu gemüte)
-
@seldon: Hast mich überzeugt, wieder was gelernt, danke

@Skym0sh0: Hab ich das jetzt richtig verstanden, dass du sowas willst wie
class Container { Base *ptr; //hier steckt irgendein Derived drin template< class Type > void init( Type Object ) { ptr->init( Object ); //Wenn Derived init(Type) überschrieben hat, dann soll das aufgerufen //werden, ansonsten soll nichts passieren. } };
-
GorbGorb schrieb:
@Skym0sh0: Hab ich das jetzt richtig verstanden, dass du sowas willst wie
class Container { Base *ptr; //hier steckt irgendein Derived drin template< class Type > void init( Type Object ) { ptr->init( Object ); //Wenn Derived init(Type) überschrieben hat, dann soll das aufgerufen //werden, ansonsten soll nichts passieren. } };nein, sowas habe ich:
class Base { public: template<typename T> virtual void init(T const& x) = 0; };class DeriveA : public Base { public: template<> virtual void init(String const& x) // braucht einen string für seine initialisierung { // do something } } class DeriveB : public Base { public: template<> virtual void init(std::vector<int> const& x) // braucht einen vector für seine initialisierung { // do something } } class DeriveC : public Base { public: template<> virtual void init(std::complex<double> const& x) // braucht eine complexe zahl für seine initialisierung { // do something } }class Manager { std::vector<Base*> vec; // wird auch irgendwo gefüllt mit den klassen... public: void init(int index) { if ( index == someThingSpecial1 ) vec[index]->init("String1"); if ( index == someThingOtherSpecial ) vec[index]->init(std::vector<int>(100, 5); if ( index == lolFooBar ) vec[index]->init(std::complex<double>(5.0, 3.0); } };das ist nur schematisch gemeint, weder getestet noch kompilierbar und auch von der genauen implementierung meines projektes abweichend
-
Wenn du ohnehin eine Fallunterscheidung hast, solltest du statische Typinformation über die abgeleiteten Objekte besitzen. Dann kannst du auch gleich die richtige
Init()-Methode aufrufen...Aber grundsätzlich sind solche Fallunterscheidungen fast immer ein Hinweis auf schlechtes Design. Was sind das semantisch für Klassen, was genau tut
Init()?