virtuelle Templates
-
nielsp schrieb:
Warum sind eigentlich virtuelle Templates verboten?
Darauf kommst du von selbst, wenn du mal darüber nachdenkst, wie man virtuelle Methoden implementieren könnte. Die kanonische Lösung ist eine VMT. Für
struct Foo { int i, j; virtual void bar (void) {} virtual void baz (void) {} };generiert ein Compiler ein Objektlayout wie das folgende:
Objekt VMT ====== === | ... | | RTTI | +---------+ +-------+ | pVMT | -> | &bar | +---------+ | &baz | | i | +-------+ | j | +---------+(Btw, könnte man nicht mal ASCII-Art-Tags einführen?)
Die VMT existiert für jede Klasse genau einmal und referenziert alle virtuellen Methoden; sie werden also alle mit ins Binary hineingelinkt - denn man weiß zu diesem Zeitpunkt ja nicht, welche virtuellen Funktionen einmal aufgerufen werden könnten.
Nun stelle dir vor, bar wäre ein Funktionstemplate. Als Funktion gibt es bar genau einmal, also belegt es einen Slot in der VMT. Als Template jedoch können unendlich viele unterschiedliche Instantiierungen existieren. Es bleiben also zwei Optionen:
- Der Compiler generiert für jede mögliche Instantiierung Code und referenziert ihn in der VMT.
- Wenn ein virtuelles Funktionstemplate aufgerufen wird, schlägt das Programm in einer Tabelle aller vorliegenden Instantiierungen nach; sofern die Instantiierung noch nicht existiert, wird sie generiert.Die Folgen beider Ansätze dürften sichtbar sein: #1 erfordert unendlich große VMTs, unendlich lange Übersetzungszeiten etc. #2 erfordert Codegeneration zur Laufzeit - und nicht nur das: C++-Templates verwenden Duck-Typing-Mechanismen zur Übersetzungszeit, also muß ein Programm, das virtuelle Funktionstemplates benutzt, einen C++-Compiler enthalten - und eine Instantiierung könnte sogar zur Laufzeit scheitern!
(So weit ich weiß, werden Generics in .NET vom JIT-Compiler nach Bedarf instantiiert. #2 dürfte also mit Generics und JIT sogar umsetzbar sein.)
-
Danke für die Antworten!
@audacia: Findet Template-Auflösung nicht zur Compilezeit statt? Also wenn der Compiler im Code sieht, dass eine Instanzierung mit T=bla erfolgt, die entsprechende Variante erzeugt und das erzeugte Produkt dann zum Compilieren weiterverwendet wird?
@drakon: Wie greife ich dann auf ein bestimmtes
command-Objekt zu? So?static_cast<command<T...>>(some_commands[some_index].get())(param1, param2, ...);Niels.
-
nielsp schrieb:
@audacia: Findet Template-Auflösung nicht zur Compilezeit statt? Also wenn der Compiler im Code sieht, dass eine Instanzierung mit T=bla erfolgt, die entsprechende Variante erzeugt und das erzeugte Produkt dann zum Compilieren weiterverwendet wird?
Sicher - aber wie soll das bei virtuellen Funktionen gehen? Nimm folgendes Beispiel:
common.hpp:
struct IFoo { template <typename T> virtual void bar (void) = 0; };dll.cpp (wird zu einer DLL gelinkt):
struct Foo : IFoo { template <typename T> virtual void bar (void) { ... } }; __declspec (dllexport) IFoo* createFoo (void) { return new Foo; }main.cpp (wird zu einer Executable gelinkt):
__declspec (dllimport) IFoo* createFoo (void); struct SomeSpecificType { ... }; int main (void) { createFoo ()->bar <SomeSpecificType> (); }Wie soll der Compiler beim Übersetzen von dll.cpp wissen, mit welchem Typ die Funktion in der Anwendung tatsächlich instantiiert wird?
-
audacia schrieb:
Wie soll der Compiler beim Übersetzen von dll.cpp wissen, mit welchem Typ die Funktion in der Anwendung tatsächlich instantiiert wird?
Gar nicht, daher müßte man dynamic dispatching in die Sprache integrieren, so daß die Methodenauflösung erst zur Laufzeit erfolgt. D.h. frage Objekt ob es Methode x unterstützt und schicke dann Nachricht an das Objekt, das ist der typische SmallTalk Weg Dispatching zu lösen: langsam aber sehr flexibel.
-
Bei
shared_ptr<command<std::string>> c(new some_command<std::string>());kommt:
sorry, unimplemented: cannot expand ‘T ...’ into a fixed-length argument list
Dabei benutze ich gcc 4.4. Wann wird C++0x endlich vollständig da sein?
Niels
-
nielsp schrieb:
sorry, unimplemented: cannot expand ‘T ...’ into a fixed-length argument list
Dabei benutze ich gcc 4.4. Wann wird C++0x endlich vollständig da sein?Zeig mal mehr Code. Ich habe beim G++ 4.3.3 schon ordentlich variadische Templates getestet und habe diese Fehlermeldung noch nie gesehen. Sehr wahrscheinlich kann man das Problem in Deinem Fall umgehen.
Gruß,
SP
-
~john schrieb:
audacia schrieb:
Wie soll der Compiler beim Übersetzen von dll.cpp wissen, mit welchem Typ die Funktion in der Anwendung tatsächlich instantiiert wird?
Gar nicht, daher müßte man dynamic dispatching in die Sprache integrieren, so daß die Methodenauflösung erst zur Laufzeit erfolgt. D.h. frage Objekt ob es Methode x unterstützt und schicke dann Nachricht an das Objekt, das ist der typische SmallTalk Weg Dispatching zu lösen: langsam aber sehr flexibel.
Dynamisches Dispatching wie in Smalltalk, JS oder PHP geht an der hier wesentlichen Problematik vorbei. Sicher, hätte man die Voraussetzungen, um generische virtuelle Funktionen zu implementieren (z.B., wie oben erwähnt, in .NET mit Generics), so würde man sich natürlich eines derartigen Dispatching-Mechanismus bedienen. Das wesentliche Problem in C++ ist aber die Bindungslosigkeit von Templates - anders als Generics in .NET sind Templates nicht viel anderes als ein turing-vollständiger und typsicherer Präprozessormechanismus, also müßte das Binary nicht nur über einen JIT-Compiler, sondern über einen nahezu vollständigen C++-Compiler (den Lexer ausgenommen) verfügen.
(Im Übrigen war das eine rhetorische Frage.)
-
audacia|off schrieb:
Das wesentliche Problem in C++ ist aber die Bindungslosigkeit von Templates - anders als Generics in .NET sind Templates nicht viel anderes als ein turing-vollständiger und typsicherer Präprozessormechanismus, also müßte das Binary nicht nur über einen JIT-Compiler, sondern über einen nahezu vollständigen C++-Compiler (den Lexer ausgenommen) verfügen.
Ich fände genau das ziemlich cool

Zugegebenermassen irgendwo total plem, aber irgendwo auch nicht
-
Hallo,
Das Problem mit dem
cannot expand ‘T ...’ into a fixed-length argument list'hat sich erledigt
. Das war ein blöder Fehler von mir.Zunächst aber erst mal mein Code:
#include <string> #include <iostream> #include <memory> struct command_base { }; template<typename... T> class command : public command_base { public: virtual void operator()(const T&... parameters); protected: std::string some_member; }; template<typename... T> struct some_command : public command<T...> { virtual void operator()(const T&... parameters) { // verwende auf irgendwelche Art die Parameter std::cout << some_member << std::endl; } }; int main() { shared_ptr<command<std::string>> c(new some_command<std::string>()); static_cast<some_command<std::string>>(c)()(std::string("1234")); return 0; }In der main()-Funktion soll
some_commandausgeführt werden.Die Fehler sind:
In member function ‘virtual void some_command<T>::operator()(const T& ...)’: error: ‘some_member’ was not declared in this scope. In function ‘int main()’: error: no matching function for call to ‘some_command<std::string>::some_command(std::shared_ptr<command<std::string>>&) ... (dann kommen die ganzen Kandidaten die es gäbe).Ich weiß zwar durchaus, was diese Fehlermeldungen zu bedeuten haben, aber ich weiß nicht, warum sie auftreten und also auch nicht, wie sie zu beheben wären.
Danke für die Hilfe!Niels
-
Was willst Du überhaupt erreichen?
(Siehe u.a. Punkt 7 von "Du brauchst Hilfe?")
Die erste Fehlermeldung kommt, weil some_member ein Element einer von einem Template-Parameter abhängigen Basisklasse ist. Du musst dem Compiler signalisieren, dass er für "some_member" die Namensauflösung verzögern soll. Das geht u.a. mit "this->some_member".
shared_ptr<command<std::string>> c(new some_command<std::string>()); static_cast<some_command<std::string>>(c)()(std::string("1234"));c ist shared_ptr. Den kannst Du nicht einfach in ein some_commandstd::string konvertieren. Außerdem: Was soll das mit dem leeren Klammerpaar? Außerdem: Wenn Du schon weißt, dass der Zeiger auf ein some_commandstd::string zeigt, wozu dann noch die -- im übrigen nicht-polymorphe! -- Basisklasse? Was Du hättest schreiben können -- angenommen command_base wär polymorph:
shared_ptr<some_command<std::string> > foo = dynamic_pointer_cast<some_command<std::string> >(c); if (foo) { (*foo)("1234"); }Du bist Dir auch im Klaren darüber, dass
command<string> * p = new some_command<string>; delete p;undefiniertes Verhalten besitzt, da Du keine virtuellen Destruktoren benutzt, ja?
Ich rate Dir, das Design komplett zu überdenken und zu erwähnen, welches Problem Du damit zu lösen gedenkst.
-
hustbaer schrieb:
audacia|off schrieb:
Das wesentliche Problem in C++ ist aber die Bindungslosigkeit von Templates - anders als Generics in .NET sind Templates nicht viel anderes als ein turing-vollständiger und typsicherer Präprozessormechanismus, also müßte das Binary nicht nur über einen JIT-Compiler, sondern über einen nahezu vollständigen C++-Compiler (den Lexer ausgenommen) verfügen.
Ich fände genau das ziemlich cool

Zugegebenermassen irgendwo total plem, aber irgendwo auch nicht
Ja, die sich ergebenden Möglichkeiten wären nahezu grenzenlos. Aber irgendwie glaube ich, daß Microsoft, Embarcadero, Intel, PathScale etc. nicht so glücklich darüber wären, ihren Benutzern das Deployment ihrer Compiler ermöglichen zu müssen

-
audacia|off schrieb:
Das wesentliche Problem in C++ ist aber die Bindungslosigkeit von Templates
Eben das haben sie mit dem Dispatching von Smalltalk gemein. Smalltalk überprüft keinerlei Konzepte während der Übersetzung. Erst während der Laufzeit wird geprüft ob eine Meldung von einem Objekt verstanden wird, und dazu bedarf es keines Compilers. Man muß nur die Methodeninformationen in den Programmcode ablegen.
audacia|off schrieb:
… also müßte das Binary nicht nur über einen JIT-Compiler, sondern über einen nahezu vollständigen C++-Compiler (den Lexer ausgenommen) verfügen.
Eben das müßte nicht der Fall sein - siehe Smalltalk. Es reicht aus dynamisches Dispatching auf Basis von Methodennamen, Rückgabetyp und Parametertypliste zu implementieren.
-
~john schrieb:
audacia|off schrieb:
Das wesentliche Problem in C++ ist aber die Bindungslosigkeit von Templates
Eben das haben sie mit dem Dispatching von Smalltalk gemein. Smalltalk überprüft keinerlei Konzepte während der Übersetzung. Erst während der Laufzeit wird geprüft ob eine Meldung von einem Objekt verstanden wird, und dazu bedarf es keines Compilers. Man muß nur die Methodeninformationen in den Programmcode ablegen.
Du hast das Problem nicht verstanden.
~john schrieb:
audacia|off schrieb:
… also müßte das Binary nicht nur über einen JIT-Compiler, sondern über einen nahezu vollständigen C++-Compiler (den Lexer ausgenommen) verfügen.
Eben das müßte nicht der Fall sein - siehe Smalltalk. Es reicht aus dynamisches Dispatching auf Basis von Methodennamen, Rückgabetyp und Parametertypliste zu implementieren.
Nein. Um virtuelle Templatefunktionen haben zu können, muß es möglich sein, sie zur Laufzeit mit beliebigen Typen zu instantiieren. Und das geht nicht ohne einen C++-Compiler.