Metaprogrammierung
-
umsteller schrieb:
Ich erweitere mal die Frage auf:
In welchen Situationen habt ihr schon Metaprogrammierung verwendet?
Zur automatischen Codegenerierung um ein Programm mit statischer Polymorphie zu erstellen, bei dem zwar die Algorithmen alle zur Programmierzeit bekannt waren, die Art und Anzahl der beteiligten Klassen aber erst zur Compilezeit.
-
SeppJ schrieb:
umsteller schrieb:
Ich erweitere mal die Frage auf:
In welchen Situationen habt ihr schon Metaprogrammierung verwendet?
Zur automatischen Codegenerierung um ein Programm mit statischer Polymorphie zu erstellen, bei dem zwar die Algorithmen alle zur Programmierzeit bekannt waren, die Art und Anzahl der beteiligten Klassen aber erst zur Compilezeit.
Sehr schönes Beispiel

-
drakon schrieb:
umsteller schrieb:
In welchen Situationen habt ihr schon Metaprogrammierung verwendet?
Hauptsächlich spielereien. IIRC habe ich das nie in einem Projekt wirklich gebraucht. In gewissen Boost Bibliotheken wird es sehr ausgedehnt gebraucht, aber üblicherweise ist es nicht nötig.
Ah... Du bist 1989 Baujahr?

-
Immer, wenn ich mit Traits zu tun habe. Das ist im Zusammenhang mit Iteratoren häufiger der Fall - oft kann man mit Random-Access-Iteratoren auf eine effizientere Funktion ausweichen, als es mit einem einfachen Input-Iterator der Fall ist.
Ich habe beispielsweise vor einer Weile einen Container geschrieben, der eine Random-Access-Sequence modelliert (etwa wie std::vector), seine Daten aber bis zu einer per Template-Parameter einstellbaren Größe lokal hält und danach auf den Heap ausweicht (der Sinn der Sache war, seltener an den Heap gehen zu müssen und so Heap-Contention zu vermeiden). Kriege ich im Range-Konstruktor einen Random-Access-Iterator, kann ich die Datenmenge vorher feststellen und ggf. gleich auf den Heap ausweichen (und da genug Speicher auf einmal anfordern), kriege ich etwa eine std::istream_iterator, geht das nicht. Also:
template<typename iter_t> local_vector(iter_t b, iter_t e) { fill(b, e, typename std::iterator_traits<iter_t>::iterator_category()); } // ... template<typename iter_t, typename iterator_category> void fill(iter_t b, iter_t e, iterator_category) { // Füllen wie aus einfachem Input-Iterator } template<typename iter_t> void fill(iter_t b, iter_t e, std::random_access_iterator_tag) { // Füllen wie aus Random-Access-Iterator }Im eigentlichen Code ist das ganze noch etwas komplexer, weil ich das im Fall eines local_vector<int, size> o.ä. vom Füllkonstruktor (local_vector<int, size> lv(20, 1); ) trennen muss.
-
Metanprogger schrieb:
in welchen Fällen greift ihr zur Metaprogrammierung?
Habt ihr bestimmte Situationen, wo ihr sie ausschließlich oder einfach
nur gerne verwendet?Ja, ab und zu passt es sehr gut. Was mir gerade einfällt:
- Type Traits
- Static Assertions
- Policy-Based Design
Und natürlich statische Polymorphie als allgemeine Template-Programmierung. Kommt halt etwas drauf an, wo man die Grenze zur Metaprogrammierung zieht.
-
Nexus schrieb:
Und natürlich statische Polymorphie als allgemeine Template-Programmierung. Kommt halt etwas drauf an, wo man die Grenze zur Metaprogrammierung zieht.
Also das ist doch keine Metaprogrammierung mehr.
-
humanspace schrieb:
Also das ist doch keine Metaprogrammierung mehr.
Hätte ich jetzt auch gesagt, aber wo genau ist die Grenze? Das ist meiner Meinung nach nicht ganz eindeutig...
-
Metanprogger schrieb:
Ah... Du bist 1989 Baujahr?

Exakt.

Ich habe die Grenze sehr weit oben angesetzt und darum das Beispiel gepostet, wo ich wirklich eine Berechnung auslagere. Natürlich benutze ich die Sachen, die Nexus anspricht ebenfalls, aber bis vielleicht auf die static Assertions gehört für mich das zu normaler Benutzung von templates.
-
Template-Metaprogrammierung ist, wenn die Fehlermeldung länger als fünf Zeilen ist.
Aber Spaß beiseite, so, wie manchmal über Metaprogrammierung gesprochen wird, hat man den Eindruck, Metaprogrammierung sei, was derart abgedreht ist, dass es kein Normalsterblicher jemals verstehen könne (geschweige denn benutzen wolle). Für mein Verständnis dagegen bedeutet Metaprogrammierung, Teile der Programmlogik auf den Compiler abzuwälzen. Eine wirklich klare Grenze wird sich aber wohl kaum definieren lassen.
-
drakon schrieb:
Ich habe die Grenze sehr weit oben angesetzt und darum das Beispiel gepostet, wo ich wirklich eine Berechnung auslagere.
Naja, solche Experimente werden ja oft als Beispiele genannt, ebenso wie die Potenzberechnung in Wikipedia. Meiner Ansicht nach gehört das aber genau zu jenen Fällen, die man in der Praxis nie verwendet. Vielleicht besteht die gewisse Skepsis oder Ablehnung gegenüber Template-Metaprogrammierung in C++ zumindest zu einem Teil, weil eine falsche Vorstellung vorherrscht (wie von seldon erwähnt).
drakon schrieb:
Natürlich benutze ich die Sachen, die Nexus anspricht ebenfalls, aber bis vielleicht auf die static Assertions gehört für mich das zu normaler Benutzung von templates.
Ich verstehe nicht, wieso Static-Assertions für dich eher dazugehören als Type Traits. Die Technik dahinter ist genau die gleiche (Typ/Konstanten-Dispatching anhand von Templatespezialisierung).
Was hier übrigens noch keiner erwähnt hat, ist Präprozessor-Metaprogrammierung. Das braucht man zwar eher selten, aber für gewisse Fälle sind Makros gute Codegeneratoren. Boost hat ja eine eigene Bibliothek dafür.
-
Nexus schrieb:
Was hier übrigens noch keiner erwähnt hat, ist Präprozessor-Metaprogrammierung. Das braucht man zwar eher selten, aber für gewisse Fälle sind Makros gute Codegeneratoren. Boost hat ja eine eigene Bibliothek dafür.
Ich sollte noch dabei sagen, dass mein oben erwähnter Codegenerator komplett im Präprozessor programmiert wurde
. Und ohne die genannte Boost-Bibliothek wäre das viel Arbeit gewesen, mit war es relativ einfach.
-
seldon schrieb:
Für mein Verständnis dagegen bedeutet Metaprogrammierung, Teile der Programmlogik auf den Compiler abzuwälzen. Eine wirklich klare Grenze wird sich aber wohl kaum definieren lassen.
Aber nicht
int berechne(int x) { int const vollversion=false; if(vollversion) return foo(17*x); else return bar(19*x); }und das Abwälzen sieht so aus, daß man weiß, daß der Compiler den Zeigr mit der always-false-condition gar nicht erzeugt.
Und auch nicht
int const size=4096/sizeof(Foo);//ganze Speicherseite auf einmal holen
-
Nexus schrieb:
drakon schrieb:
Ich habe die Grenze sehr weit oben angesetzt und darum das Beispiel gepostet, wo ich wirklich eine Berechnung auslagere.
Naja, solche Experimente werden ja oft als Beispiele genannt, ebenso wie die Potenzberechnung in Wikipedia. Meiner Ansicht nach gehört das aber genau zu jenen Fällen, die man in der Praxis nie verwendet. Vielleicht besteht die gewisse Skepsis oder Ablehnung gegenüber Template-Metaprogrammierung in C++ zumindest zu einem Teil, weil eine falsche Vorstellung vorherrscht (wie von seldon erwähnt).
Ich sagte, dass das eher Spielerei ist, um zu zeigen, was man damit machen kann. Template-Metaprogrammierung ist da halt eher, dass man das eigentliche Programm wirklich auslagert und den Compiler nicht nur als Hilfsmittel benutzt. Man schiebt die eigentliche Logik komplett auf den Compiler und benutzt die Sprache lediglich als Hilfsmittel, um das Ergebnis anzuzeigen. Auf der Stufe ist es natürlich (noch) nicht wirklich zu gebrauchen, aber ich könnte mir gut vorstellen, dass mit der Zeit der Aspekt vielleicht doch ein wenig an Stärke gewinnt.
drakon schrieb:
Natürlich benutze ich die Sachen, die Nexus anspricht ebenfalls, aber bis vielleicht auf die static Assertions gehört für mich das zu normaler Benutzung von templates.
Ich verstehe nicht, wieso Static-Assertions für dich eher dazugehören als Type Traits. Die Technik dahinter ist genau die gleiche (Typ/Konstanten-Dispatching anhand von Templatespezialisierung).
Weil ich nie wirklich den Drang hatte es die benutzen zu müssen, wogegen Traits doch immer mal wieder sehr nett sind. Daher gehört es für mich eher auf die Seite der spielereien.
-
volkard schrieb:
und das Abwälzen sieht so aus, daß man weiß, daß der Compiler den Zeigr mit der always-false-condition gar nicht erzeugt.
Trotzdem muss der Code in diesem Zweig noch kompilierbar sein. Diese Einschränkung hast du mit Template-Spezialisierungen, die den Ablaufpfad bestimmen, nicht (was oft ein Grund ist, diese einzusetzen). Wobei bei deinem ersten Beispiel wohl bedingte Kompilierung über
#ifdefam geeignetsten wäre.drakon schrieb:
Weil ich nie wirklich den Drang hatte es die benutzen zu müssen, wogegen Traits doch immer mal wieder sehr nett sind. Daher gehört es für mich eher auf die Seite der spielereien.
Traits können ja auch nett und Metaprogrammierung sein. Oder impliziert der Begriff Metaprogrammierung für dich, dass es sich um Spielereien handelt?

-
wenn ich in meinem aktuellen Projekt nach 'boost/mpl' suche, ist das Ergebnis recht dünn. Aber zumindest einen konkreten und auch sinnvollen Anwendungsbereich gibt es:
wir haben ein kleine Lib, mit der wir Variablen aus einer (xml-)Konfigdatei lesen. Da wir typsicher lesen wollen, läuft das intern auf einen lexical_cast hinaus. Nun ist es bei einigen Werten so, dass rudimentäre Werte (char, int oder double) gelesen werden sollen, aber diese in einem anderen Format in der xml-Datei stehen.
Im konkreten Fall bedeutet das, dass im Programm ein double (für die Länge in Metern) genutzt wird und in der Datei "21.5cm" steht - der gewünschte Wert für den double wäre also 0.215. Dafür haben wir auch Manipulatoren (z.B. Meters), die das machen würden, aber man kann normalerweise nicht schreiben:
double lenInMetern = lexical_cast< Meters >( config["Laenge"] );das habe ich per Metaprogprammierung so gelöst, dass an den Code-Stellen wo gelesen wird, eine Eigenschaft des Templateparameters abgefragt wird und dann eben der Manipulator (hier Meters) genutzt wird und der Rückgabewert ist dann 'Meters::value_type == double' statt 'Meters'. Somit funktioniert der Code wie er oben steht.
Gruß
Werner
-
Nexus schrieb:
drakon schrieb:
Weil ich nie wirklich den Drang hatte es die benutzen zu müssen, wogegen Traits doch immer mal wieder sehr nett sind. Daher gehört es für mich eher auf die Seite der spielereien.
Traits können ja auch nett und Metaprogrammierung sein. Oder impliziert der Begriff Metaprogrammierung für dich, dass es sich um Spielereien handelt?

Mit der relativ hohen Grenze, welche ich benutzt habe ja. Wie gesagt für mich (!) ist das Metaprogrammierung, wenn man wirklich Programmlogik auslagert. Da die Grenzen ja nicht definiert sind kann man das natürlich anders sehen, aber so etwas, wie Werner anspricht ist für mich nicht wirklich Metaprogrammierung (ohne den Code gesehen zu haben).
-
Boost Spirit ist meta-programmiert. Du gibst in Source Code eine EBNF-ähnliche Form ein und daraus wird ein Parser zur Compilerzeit gebaut.
-
Ein sehr schoenes Beispiel ist z.B. Typsicherheit bei physikalischen Berechnungen wie sie in Boost's MPL angegeben ist. D.h. wenn ich eine Kraft mit einem Weg vergleiche, dann soll schon der Compiler den Fehler feststellen.
Boost's Spirit halte ich dagegen nicht so toll. Es gibt Parser wie Sand am mehr, die sich ebenfalls gut in C oder C++ einbinden lassen.
-
umsteller schrieb:
Ich erweitere mal die Frage auf:
In welchen Situationen habt ihr schon Metaprogrammierung verwendet?
Für enum-Typ mapping. In einem Programm hatte ich ein kleines Protokoll mit Befehlen, die durch ein Enum codiert waren. Je nach Befehl wurden zusätzliche Daten benötigt, die völlig unterschiedliche Typen haben konnten. Das Ganze ging dann in etwa so:
enum ECommand { /* ... */ }; struct Command { ECommand ec; virtual ~Command() {} }; struct Empty_t {}; template <ECommand EC> struct Ec2Type { typedef Empty_t type; }; //partial specalizations of Ec2Type... template <ECommand EC> struct ParametrizedCommand : public Command { typename Ec2Type<EC>::type param; };Die ParametrizedCommand's wurden dann per shared_ptr<Command> durch die Gegend gereicht, es gab dazu noch eine create- und eine extract-Funktion, die die Commands mit Parametern erstellt haben bzw. die Parameterwerte daraus extrahiert haben.
Vermutlich wäre es auch einfacher gegangen, aber ich spiele halt gerne mit TMP rum
knivil schrieb:
Boost's Spirit halte ich dagegen nicht so toll. Es gibt Parser wie Sand am mehr, die sich ebenfalls gut in C oder C++ einbinden lassen.
Und selbst wenns noch Meer Parser gäbe, wäre das kein Argument dass Spirit schlecht ist
Ich weiß nicht wie gut sich Spirit in produktivem COde einsetzen lässt, aber schon das Konzept ist eines der besten Beispiele wie viel erstaunliche Dinge man erreichen kann wenn man die verschiedenen Paradigmen von C++ richtig kombiniert.