C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005:...

29
C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum Ziel, aus einer abstrakten Spezifikation mittels sogenannter Generatoren automatisch ein fertiges Produkt zu bauen. Ein Generator ist dabei ein Programm, das als Eingabe eine high-level” Spezifikation erh¨ alt und daraus die fertige Implementierung erzeugt. Zus¨ atzlich soll dieser Prozess konfigurierbar sein und ein m¨ oglichst optimales Ergebnis liefern. Ein Metaprogramm ist ein Programm ¨ uber ein Programm, d.h. ein Programm, das andere Programme oder sich selbst an sich ¨ andernde Anforderungen anpassen kann. Ein Metaprogramm l¨ asst sich deshalb als spezieller Generator auffassen: Eingabe ist ein Programm in einer Hochsprache, Ausgabe ist ein anderes Programm, im Optimalfall bereits das Endprodukt. Metaprogrammierung ist deshalb ein wichtiger Baustein der generati- ven Programmierung. Erst Mitte der 90er Jahre wurde klar, dass es in C++ m¨ oglich ist, Metaprogramme in der Sprache zu formulieren, ohne C++ selbst zu verlassen. Metaprogrammierung in C++ er¨ offnet ¨ uberraschend viele Anwen- dungsm¨ oglichkeiten. Da sie jedoch eher zuf¨ allig in die Sprache Einzug hielt, ist sie auch mit einigen Nachteilen verbunden, von denen die geringe Lesbarkeit der Metaprogramme die augenf¨ alligste sein d¨ urfte. Mit zunehmender Unterst¨ utzung durch die g¨ angigen Compiler ist jedoch damit zu rechnen, dass C++-Metaprogrammierung mehr und mehr zum Alltag des C++-Programmierers geh¨ oren wird.

Transcript of C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005:...

Page 1: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

C++ - Metaprogrammierung

Hoffmann GerhardBlockseminar am 23.06.2005:Fortgeschrittene Konzepte in C++

Zusammenfassung

Generative Programmierung hat zum Ziel, aus einer abstraktenSpezifikation mittels sogenannter Generatoren automatisch ein fertigesProdukt zu bauen. Ein Generator ist dabei ein Programm, das alsEingabe eine

”high-level” Spezifikation erhalt und daraus die fertige

Implementierung erzeugt. Zusatzlich soll dieser Prozess konfigurierbarsein und ein moglichst optimales Ergebnis liefern.

Ein Metaprogramm ist ein Programm uber ein Programm, d.h. einProgramm, das andere Programme oder sich selbst an sich anderndeAnforderungen anpassen kann.

Ein Metaprogramm lasst sich deshalb als spezieller Generatorauffassen: Eingabe ist ein Programm in einer Hochsprache, Ausgabeist ein anderes Programm, im Optimalfall bereits das Endprodukt.Metaprogrammierung ist deshalb ein wichtiger Baustein der generati-ven Programmierung.

Erst Mitte der 90er Jahre wurde klar, dass es in C++ moglich ist,Metaprogramme in der Sprache zu formulieren, ohne C++ selbst zuverlassen.

Metaprogrammierung in C++ eroffnet uberraschend viele Anwen-dungsmoglichkeiten. Da sie jedoch eher zufallig in die Sprache Einzughielt, ist sie auch mit einigen Nachteilen verbunden, von denen diegeringe Lesbarkeit der Metaprogramme die augenfalligste sein durfte.

Mit zunehmender Unterstutzung durch die gangigen Compiler istjedoch damit zu rechnen, dass C++-Metaprogrammierung mehr undmehr zum Alltag des C++-Programmierers gehoren wird.

Page 2: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Inhaltsverzeichnis

1 Was ist ein Metaprogramm? 2

2 Warum Metaprogrammierung in C++ 4

3 Template Metaprogrammierung in C++ 53.1 Expression Templates . . . . . . . . . . . . . . . . . . . . . . 63.2 DSL/DSEL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123.3 Boost Metaprogramming Library . . . . . . . . . . . . . . . . 14

4 Metaprogrammierung mit dem Praprozessor 154.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154.2 Horizontale Iteration . . . . . . . . . . . . . . . . . . . . . . . 164.3 Vertikale Iteration . . . . . . . . . . . . . . . . . . . . . . . . 18

4.3.1 Lokale Iteration . . . . . . . . . . . . . . . . . . . . . . 184.3.2 Funktionsweise der lokalen Iteration . . . . . . . . . . 194.3.3 Datei-Iteration . . . . . . . . . . . . . . . . . . . . . . 214.3.4 Funktionsweise der Datei-Iteration . . . . . . . . . . . 214.3.5 Selbstbezugliche Iteration . . . . . . . . . . . . . . . . 234.3.6 Mehrdimensionale Iteration . . . . . . . . . . . . . . . 25

5 Nachteile der Metaprogrammierung in C++ 27

1

Page 3: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

1 Was ist ein Metaprogramm?

Ein Metaprogramm ist ein Programm, das Code manipuliertoder repraseniert.[6]

Metaprogrammierung bedeutet das Andern eines bestehenden Programmsdurch ein Programm. Dabei ist ausdrucklich die Moglichkeit eingeschlossen,dass ein Metaprogramm seinen eigenen Code verandert.

Voraussetzung fur solch eine Selbstbezuglichkeit eines Programms ist,dass der Ausfuhrungszustand des Programms als Datum kodiert vorliegt,auf das das Programm selbst lesenden oder sogar schreibenden Zugriff hat,und so seinen eigenen Zustand beurteilen beziehungsweise sogar verandernkann.

Es ist deshalb nicht uberraschend, dass Metaprogrammierung vor al-lem in Sprachen dominant ist, in denen Code und Daten von Haus ausaquivalent sind, da in diesen Sprachen jedes Programm seinen eigenenCode verandern kann und das auch permanent tut. Prominentestes Beispielhierfur ist LISP, wobei in den funktionalen Programmiersprachen Metapro-grammierung als High-Order-Programmierung bezeichnet wird.

Meist liegt aber kein Selbstbezug vor. Beispiele fur solche Metapro-gramme sind:

• Das Metaprogramm C-Praprozessor

Der C-Praprozessor erzeugt aus Quell- und Kopfdateien die Eingabefur einen Compiler.

Das C/C++-Quellprogramm wird vom C-Praprozessor interpre-tiert, also als Metaprogramm ausgefuhrt. Das Quellprogramm wirdbei diesem Verarbeitungsschritt durch die eingebetteten Prapro-zessordirektiven in eine neue Form ubertragen.

• Das Metaprogramm C++-Compiler

Ein C++-Compiler arbeitet bei Verwendung von Templates inzwei Phasen:

Zunachst instanziiert der Compiler die im Quellcode angegebe-

2

Page 4: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

nen Templates1. Dieser Schritt stellt die erste Interpretation desC++-Quellcodes durch den Compiler dar, und ist gleichzeitig diePhase, bei der ein C++-Metaprogramm zur Ausfuhrung durch denCompiler kommt und dabei in ein neues Quellprogramm ubertragenwird.

Im zweiten Schritt interpretiert der C++-Compiler den im vor-hergehenden Schritt erzeugten C++-Quellcode erneut und generiertdaraus Assembler- oder direkt Maschinencode.

• Das Metaprogramm YACC

Die Eingabedatei fur YACC enthalt Grammatikregeln in einerEBNF-ahnlichen Form. YACC erzeugt daraus C-Quellcode. Wirddieser von einem C-Compiler ubersetzt, ergibt sich ein Parser furdiese Grammatik.

Die Eingabe fur ein Metaprogramm ist in der sogenannten”domain

language” 2 geschrieben, die Ausgabe erscheint in der”host language” 3.

Domain language ⇒ Metaprogramm ⇒ Host language

1Falls er muss:”It is done as soon as necessary, but no sooner”[9].

2

”Domain language” meint also die Sprache, in der das eigentliche Problem formu-

liert ist beziehungsweise formuliert werden soll. Im folgenden wird der englischsprachigeAusdruck beibehalten.

3Dieser Sprachgebrauch wird weiter unten erlautert. Siehe (2.1.2).

3

Page 5: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

2 Warum Metaprogrammierung in C++

Wie oben bereits erwahnt, ist das Hauptziel der Metaprogrammierungdie Implementierung von Generatoren, die dann wiederum Grundlage furadaptierbare und adaptive Systeme darstellen. Hinsichtlich Kontextund Ausfuhrungszeitpunkt von Metaprogrammen (speziellen Generatoren)gibt es anders als in funktionalen Programmiersprachen in C++ eineEinschrankung: Bei der Metaprogrammierung in C++ handelt es sich umeine sogenannte statische Metaprogrammierung. Das bedeutet, dassein Metaprogramm - im folgenden werden dies der C-Praprozessor oder derC++-Compiler sein - vor dem Programm, das es manipuliert, ausgefuhrtwird.

Im Gegensatz etwa zu YACC unterscheiden sich bei einem C++-Metaprogramm

”domain language” und

”host language” aber nicht (sie

ist beide Male C++). Arbeitet der Programmierer auf der Metaebeneder

”domain language”, so ist er hier auf die sogenannten Metadaten

beschrankt.

Metadaten bezeichnen dabei alle Daten, die sich zur Kompilierzeitmanipulieren lassen, also Typen, enums, Zeiger und Referenzen auf Funk-tionen und externe Objekte, Zeiger auf Klassenelemente sowie konstanteintegrale Werte [7, pp.108].

Domain Language: C++⇒ Praprozessor/Compiler⇒ Host Language: C++⇒ Compiler⇒ Assembler Code/Binardatei

Durch Praprozessor und Templates zerfallt C++ in zwei Ebenen.

Auf Ebene 1, der Kompilierphase, befindet sich die statische Meta-programmierung mittels Templates und Praprozessor.4[9].

4Die Programmiersprache, die sich mittels der Templates und weiterer Eigenschaf-ten von C++ ergibt, bildet eine Turing-vollstandige Sprache. Eine Turing-vollstandigeSprache ist eine Sprache mit wenigstens einem konditionalem Konstrukt und wenigstenseinem Schleifenkonstrukt. Diese Sprache ist damit aquivalent zu jeder anderen Turing-vollstandigen Sprache wie etwa LISP oder C++.

4

Page 6: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Ebene 2 stellt die gewohnte Ebene der Ubersetzung eines C++-Quellprogramms durch einen Compiler dar.

Obwohl die Metaprogrammierung in C++ in der Praxis nur auf die sta-tische Ebene 1 anwendbar ist, bietet sie dennoch ein breites Anwendungs-spektrum.

• Verbesserung der Ausfuhrungsgeschwindigkeit

i) Berechnungen, die ublicherweise zur Laufzeit vorgenommen wer-den, lassen sich wahrend des Kompiliervorgangs vorwegnehmen(z.B. Konstanten im Programm).

ii) Vermeidung von temporaren Objekten mittels Expression Tem-plates.

iii) Loop-Unrolling: Schleifen werden durch linearen Code ersetzt [4].

• Korrektheit und Wartbarkeit

Durch Templates wird maschinell Code erzeugt. Dadurch erhoht sichsowohl die Wartbarkeit als auch Korrektheit des Quellcodes.

• DSL/DSEL

DSL/DSELs (domain-specific embedded languages) zeichnensich durch einen hohen Abstraktionsgrad und eine große Nahezum eigentlichen Problemfeld aus (z.B Hardwarebeschreibungs-sprachen). Mittels Metaprogrammierung ist es moglich, innerhalbvon C++ solche DS(E)Ls zu definieren, die vorliegende Problem-stellung in der DSL/DS(E)L zu formulieren und anschließenddirekt mit dem C++-Compiler in Maschinencode zu ubersetzen.

3 Template Metaprogrammierung in C++

Templates waren in C++ ursprunglich als Schablonen fur Klassen oderFunktionen gedacht. Der Compiler generiert aus dem Template einegewohnliche Klasse oder Funktion, die er dann in einem zweiten Schrittbehandelt wie jede andere von Hand kodierte Klasse oder Funktion auch.

1994 wurde von Erwin Unruh [10] eher zufallig entdeckt, dass manmit Klassen-Templates5 zur Kompilerzeit numerische Berechnungen aus-fuhren kann. Die zentrale Idee dahinter ist, die (Typ-)Parameter einesTemplates analog zu Parametern einer Funktion, die zur Laufzeit ausgefuhrt

5Im folgenden wird statt Klassen-Template immer der Begriff des Templates benutzt.Falls auch Funktionstemplates relevant sind, wird explizit darauf verwiesen.

5

Page 7: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

wird, aufzufassen.

Templates werden also zu”Funktionen”, die auf der Meta-Ebene des

dynamischen Codes angesiedelt sind: sie erzeugen Source-Code fur einProgramm, das vom Compiler erst noch generiert werden muss. Im einfach-sten Fall handelt es sich zum Beispiel etwa um Konstanten, die wahrendder Kompilierphase plattformabhangig vom Template berechnet werden.Templates werden deshalb auch als Metafunktionen bezeichnet.

1995 wurde von Todd Veldhuizen [11],[12] ein Artikel unter dem Titel

”Using C++ Template Metaprogramms” veroffentlicht, der zum Thema

sogenannte Expression-Templates hatte. Expression-Templates stellen dieGrundlage fur die Formulierung von DSELs in C++ dar.

3.1 Expression Templates

Zahlreiche Artikel uber Expression Templates (siehe etwa [4],[5],[1],[8],[3])sind seither veroffentlicht worden. Die grundlegende Struktur soll imfolgenden vorgestellt werden [6, Abrahams].

Seien x, a, b, und c eindimensionale Arrays. Betrachte folgendes State-ment:

(*) x = a + b + c

Ein naiver Weg, operator+ zu implementieren, ist etwa der folgende: 6

Array operator+(Array const& a, Array const& b) {

std::size_t const n = a.size();

Array result;

for(std::size_t i=0; i != n; ++i) {

result[i] = a[i] + b[i];

}

return result;

}

Das Problem in (*) ist, dass a + b in einem temporaren Objekt T1gespeichert, dann c + T1 in ein zweites temporares Objekt T2 gesetzt unddieses dann an x zugewiesen wird7.

6Im Original wird ein 2-dim. Array benutzt. Ein 1-dim. Array vereinfacht aber dieImplementierung, da in diesem Fall operator[] uberladen werden kann.

7Dieses Problem wird von A. Alexandrescu trickreich umgangen, jedoch nur fur denFall, dass es sich bei obigem Zuweisungsoperator um einen Kopierkonstruktor handelt.[2]

6

Page 8: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Effizient ware es, die temporaren Objekte alle zu vermeiden und dieBerechnung in einem Schritt durchzufuhren, also nur einmal uber a, b, undc zu iterieren.

for(std::size_t i=0; i != n; ++i) {

result[i] = a[i] + b[i] + c[i];

}

Falls man (a + b) sofort berechnet, kommt man um ein temporaresObjekt nicht herum. Da dieser direkte Weg offenbar also nicht zum Zielfuhrt, versucht man einen Umweg:

We can solve any problem by introducingan extra level of indirection.

Butler Lampson

Statt (a + b) also als Summe zweier Arrays aufzufassen, begibt man sichauf eine hohere Ebene und fasst (a + b) als Funktionsobjekt auf, das dieeigentliche Berechnung zum einem spateren Zeitpunkt gestattet.

Dieses Funktionsobjekt, ein sog. Expression-Objekt, stellt einen Parse-Baum dar, der fur obiges Beispiel das bekannte Aussehen hat:

=

x

+

R

R

R

c+

a b

Parse-Baum fur x = a + b + c7

Page 9: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

struct plus {

static int apply(int a, int b) {

return (a + b); }

};

template < typename L, typename OpTag, typename R >

struct Expression {

Expression(L const& l, R const& r) : l(l), r(r) {}

int operator[](unsigned index) const {

return OpTag::apply(l[index],r[index]);

}

L const& l;

R const& r;

};

template< typename L, typename R >

Expression<L,plus,R> operator+(L const& l, R const& r) {

return Expression<L,plus,R>(l,r);

}

Beachte:

(a + b) ist nun ein Ausdruck, der in einem Objekt8 vom Typ

Expression<Array,plus,Array>

gespeichert ist. Die Struktur des Ausdrucks zeigt obiger Parse-Baum.Auf die eigentlichen Operanden (a und b bzw. b und c) besteht durch

die eingebetteten Referenzen (l und r) weiterhin Zugriff. Der Ausdruck lasstsich zu jedem beliebigen Zeitpunkt neu auswerten, ohne dafur neue Objektegenerieren zu mussen.

Analog zu (a + b) ist der Typ von (a + b + c) nun

Expression<Expression<Array,plus,Array>,plus,Array>

und Zugriff auf die Operanden besteht via den verschachtelten Referenzen.

8In funktionalen Programmiersprachen wie etwa LISP ist ein solches Objekt auch alsLambda-Ausdruck bekannt. Mittels der Boost-Library wurde man dasselbe mit ( 1 +2)(a,b) erreichen.

8

Page 10: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Damit wird etwa (a + b)[i] wie folgt ausgewertet:

(a + b)[i] == plus::apply(a[i],b[i])

== a[i] + b[i]

Nach althergebrachtem Muster wurde (a + b)[i] ein temporares Objekt(a + b) erzeugen. Alle Elemente bis auf das i-te waren aber uberflussiger-weise berechnet worden.

(a + b + c)[i] wird ebenso ohne temporare Objekte berechnet:

(a + b + c)[i] == ((a + b) + c)[i]

== plus::apply((a + b)[i], c[i])

== plus::apply(plus::apply(a[i],b[i]), c[i])

== plus::apply(a[i] + b[i], c[i])

== (a[i] + b[i]) + c[i]

Das Besondere ist nun, dass die Auswertung verzogert erfolgt, namlichdurch den Aufruf eines Zuweisungsoperators, der wie folgt definiert ist.

template<typename Expr> {

Array& Array::operator=(Expr const& x) {

for(unsigned i = 0; i < this->size(); ++i) {

(*this)[i] = x[i];

return (*this);

}

Expression Templates ermoglichen es also, operator+ so zu uberladen, dasssich hocheffizienter Code ergibt.

Der Wechsel in der Bedeutung von (a + b) ist nicht zu unterschatzen.Hierdurch werden wahrend der Kompilierphase Parse-Baume von Aus-drucken erzeugt (und zwar nicht innerhalb des Compilers), die sich dannmit Template-Metafunktionen bearbeiten lassen9.

Aufgrund dieser Eigenschaft, Parse-Baume auf der Meta-Ebene mani-pulieren zu konnen, eroffnen Expression-Templates einen fast naturlichenZugang zu neuen Sprachen innerhalb von C++, sog. domain-specific(embedded) languages (DSL/DSEL).

9Um das Beispiel einfach zu halten, wurde plus als struct angegeben, die eine apply-Funktion enthalt. An dieser Stelle kommen in

”echtem” Code Template-Metafunktionen

zum Einsatz, siehe etwa [6]

9

Page 11: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Bevor dieser Gesichtspunkt der Expression-Templates naher erlautertwird, zunachst ein kleiner Praxistest, der bereits ohne den Einsatz aufwan-diger Werkzeuge die Leistungsfahigkeit obiger Optimierung zeigt. Zum Testwurde folgende Routine benutzt, die Ergebnisse sind weiter unten darge-stellt.

int main(int argc, char** argv) {

int s;

if(argc == 2) s = atoi(argv[1]);

Array a(s),b(s),c(s),x(s);

hrtime_t start;

hrtime_t stop;

int i;

start = gethrtime();

for(i=0;i<100;++i) {

x = a + b + c;

}

stop = gethrtime();

std::cout ≪ (stop-start)/1000000L ≪ std::endl;

return 0;

}

Der Test wurde ausgefuhrt auf einer Sparc Ultra60 mit 450MHz undeiner CPU. Als Betriebssystem kam Sun Solaris 8 zum Einsatz, als Compilergcc 3.4.1.

Matrixdimension Zeit (nicht opt.)[s] Zeit (opt.)[s]

100x100 1.776 1.059200x200 7.204 4.237300x300 17.091 9.796400x400 30.305 18.087500x500 46.842 28.154600x600 85.316 54.125700x700 114.167 73.604

Ersichtlich ergibt sich bereits hier ein enormer Geschwindigkeitsvor-teil. Weitere Test mit einer Anwendung von Expression-Templates aufStrings[8] ergaben sogar Verbesserungen von rund 100%. Es ist deshalb

10

Page 12: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

nicht uberraschend, dass obige Technik der Expression-Templates vor allembei wissenschaftlich-numerischen Berechnungen zum Einsatz kommt, beidenen Performanz ein entscheidender Faktor sind.

Es besteht kein Hindernis, alle anderen uberladbaren Operatoren inanaloger Weise wie operator+ zu behandeln.Expression Templates fuhren deshalb zu neuen Sprachen innerhalb vonC++. Nach einiger Zeit der Arbeit mit diesen Sprachen verliert sich fastder Eindruck, noch uberhaupt mit C++ zu arbeiten10.

Dazu ein kleines Beispiel, entnommen der Blitz++-Bibliothek [11], derersten sog. DSEL (domain-specific embedded language), die mittels C++Metaprogrammiertechniken erstellt wurde.

Eine 3x3 Matrix schreibt sich in Blitz++ wie folgt:

Array<float,2> A(3,3);

A = 1, 2, 3,

4, 5, 6,

7, 8, 9;

(hier wurde ahnlich wie oben statt dem Plus- der Komma-Operatoruberladen).

Die Matrix ist bildlich im Quellcode zu sehen. Analog sind viele wei-tere Operationen, die der Praktiker in Zusammenhang mit Matrizen kennt,(Vertauschen von Spalten, Extraktion von Minoren etc.), viel direkterformulierbar als das unter gewohnlich hier zum Einsatz kommendenSystemen (wie etwa FORTRAN) der Fall ist, und das alles ohne jeglichenPerformanzverlust zur Laufzeit.

Nachfolgend soll kurz ein Beispiel aus einer etwas machtigerern DSELdargestellt werden. Eine vollstandige Beschreibung der DSEL ware an dieserStelle zu umfangreich. Der interessierte Leser wird deshalb auf [6] verwiesen.

10Scott Meyers sprach in einem ahnlichen Zusammenhang von dieser Art zu program-mieren als LSD-Programmierung[7].

11

Page 13: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

3.2 DSL/DSEL

Zunachst die formale Definition einer DSL/DSEL [6]:

Definition:

Eine DSL vereinigt in sich: eine domain-spezifische Notation,domain-spezifische Konstrukte sowie Abstraktion als grundlegendesEntwurfskriterium.

Eine domain-spezifische eingebettete Sprache (DESL)ist eine Bibliothek, die dieselben Kriterien erfullt.

Eine DSL ist also eine Sprache mit einem hohen Abstraktionsgradund einer hohen Ausdruckskraft. Programme konnen dadurch problem-orientiert formuliert werden. Eine DSL beschreibt mehr, was zu tun ist,als wie es zu tun ist: Die Sprache ist deklarativ. Typisches Beispiel fur solcheine DSL ist etwa eine Hardwarebeschreibungssprache wie VHDL.

Nun ist es nicht unbedingt erforderlich, eine DSL in C++ zu implemen-tieren.

Wie oben bereits angedeutet , erzeugt das Unix-Tool YACC aus einerEingabedatei (der Inhalt dieser Datei ist die Grammatik einer Sprache, der

”domain-language”) ein C/C++-Programm, das dann von einem Compiler

ubersetzt einen Parser fur die”domain-language” liefert. Diese Sprache lasst

sich nun als eine Art Aufsatz auf C++ auffassen.

Dabei ergeben sich aber verschiedene Nachteile: Zunachst muss derDSL-Designer die Funktionsweise von YACC kennen. Danach muss die DSLselbst auf den Prufstand: es gibt normalerweise keine Werkzeuge, um die inder DSL geschriebenen Programme zu testen oder zu debuggen.

Bei einer DSEL geht man deshalb den umgekehrten Weg: die DSL wird(als C++-Bibliothek) in (die Hostlanguage) C++ eingebettet (embedded).

Das bringt viele Vorteile, u.a kann der Programmierer auf alle Artenvon Werkzeugen, die er in der Hostlanguage C++ benutzt, unverandertin der DESL weiter zuruckgreifen (Editoren, Debugger, Compiler etc).Eine DESL ist also eine Erweiterung der Hostlanguage, die aber para-doxerweise innerhalb der Hostlanguage liegt. Wie das genau gemeint ist,wird durch nachfolgendes Beispiel verdeutlicht [6]. Es handelt sich umdie graphische Darstellung eines Transitionsdiagramms fur einen CD-Player.

Aufgabe des Applikationsprogrammierers ist es, aus dieser abstraktenBeschreibung des Problems eine Implementierung zu erstellen.

12

Page 14: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Empty

cd detected

- Stopped

=

� U

K

Open

open/

close

open/close

open/

close

stop

stop play

play

stopopen/close

open/close

Paused

)

qi

�Playing

C++ kann keine Bilder parsen, weshalb man eine etwas andere Notationbenutzt, eine sogenannte Zustandsubergangstabelle.

Current State Event Next State Transition Action

Stopped play Playing start playback

Stopped open/close Open open drawer

Open open/close Empty close drawercollect CD information

Empty open/close Open open drawer

Empty cd-detected Stopped store CD information

Playing stop Stopped stop playback

Playing pause Paused pause playback

Playing open/close Open stop playbackopen drawer

Pause play Playing resume playback

Paused stop Stopped stop playback

Paused open/close Open stop playbackopen drawer

13

Page 15: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Das Ziel ist nun, es dem Programmierer zu ermoglichen, dieses abstrakteDesign der Zustandsmaschine in einer DESL zu formulieren. Die DESL istdann fur die weitere Verarbeitung verantwortlich.

Wie bereits angedeutet, wurde es den Rahmen dieses Aufsatzes spren-gen, vollstandige Quellcodes anzugeben. Hier ein kleiner Ausschnitt:

struct transition_table : mpl::vector11<

//transition table// Start Event Next Action

row < Stopped, play, Playing, &p::start_playback>,

row < Stopped, open/close, Open, &p::open_drawer>,

// ...

// ...

row < Paused, open_close, Open, &p::stop_and_open>

>;

// ...

Wie man sieht, tragt der Programmierer einfach die Ubergangstabellein ein C++-Template ein, moglicherweise ohne sich dessen uberhauptbewusst zu sein.

Ist die Tabelle gefullt, tritt der Compiler in Aktion und generiert ausdiesem Template den Code fur eine FSM (finite state machine). Wie genaudas passiert, ist fur den Programmierer vollig irrelevant, und Aufgabe desDESL-Designers. Hier sieht man direkt einen Generator in Aktion, der auseiner abstrakten Spezifikation ein fertiges Produkt baut.

3.3 Boost Metaprogramming Library

Obige Ausfuhrungen sind naturlich nicht mehr als ein Anfang.Wer tiefer in die Materie einsteigen mochte, findet in der MPL (Metapro-gramming Library) innerhalb der Boost-Library eine Fulle weitergehendenMaterials [13].

14

Page 16: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

4 Metaprogrammierung mit dem Praprozessor

4.1 Motivation

Betrachte den nachfolgenden Satz von Deklarationen11:

template< class R>

yes_type is_function_tester(R (*)());

template< class R ,class A0>

yes_type is_function_tester(R (*)(A0));

template< class R ,class A0 ,class A1>

yes_type is_function_tester(R (*)(A0, A1));

template< class R ,class A0 ,class A1 ,class A2>

yes_type is_function_tester(R (*)(A0, A1, A2));

Der Zweck von”is function tester” ist zu prufen, ob ein Typ in einen

Funktionszeiger konvertierbar ist. Es ist aber leider nicht moglich, dieParameterliste einer Funktion in C++ allein mit C++-Mitteln direkt zumanipulieren, weshalb der Programmierer scheinbar vor einer ermudendenAufgabe steht: das manuelle Hinzufugen von jeweils fehlenden Parameterli-sten.

Zur Losung dieses Problems wird innerhalb der Boost-Bibliotheken [14]der Praprozessor als Metaprogramm eingesetzt. Grunde dafur sind u.a.: 12

13

• Der Praprozessor ist portabel.

• Der Praprozessor wird automatisch wahrend der Kompilierung aufge-rufen

• Der Praprozessor-Metacode kann direkt in den C++-Quellcode einge-bettet werden.

• Das Debuggen der Praprozessor-Ausgabe kann mittels des Editors er-folgen.

Im folgenden soll die”Boost Preprocessor Library” (BPL) [14] kurz

vorgestellt werden.

11Das folgende ist entnommen der BPL-Dokumentation[14]12Die Nachteile des Praprozessors sind bekannt: Nebeneffekte, geringe Lesbarkeit und

”Namespace-Pollution” durch eine große Anzahl von Makros.13Gegenwartig verwendet C++ noch den Praprozessor von C89, was sich aber mit dem

nachsten großeren Review des Standards andern soll. C++ wird dann den Praprozessorvon C99 benutzen.

15

Page 17: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

4.2 Horizontale Iteration

Obiges Beispiel nimmt mit der BPL folgende Form an:

#include <boost/preprocessor/arithmetic/inc.hpp>

#include <boost/preprocessor/punctuation/comma_if.hpp>

#include <boost/preprocessor/repetition.hpp>

#ifndef MAX_IS_FUNCTION_TESTER_PARAMS

#define MAX_IS_FUNCTION_TESTER_PARAMS 15

#endif

#define IS_FUNCTION_TESTER(Z, N, _)

template<

class R

BOOST_PP_COMMA_IF(N)

BOOST_PP_ENUM_PARAMS(N, class A)

>

yes_type is_function_tester(R (*)(BOOST_PP_ENUM_PARAMS(N, A)));

/**/

BOOST_PP_REPEAT(

BOOST_PP_INC(

MAX_IS_FUNCTION_TESTER_PARAMS), IS_FUNCTION_TESTER, _)

#undef IS_FUNCTION_TESTER

Wird obiger Code vom Praprozessor geparst, wird durch die MakrosBOOST PP REPEAT und BOOST PP INC folgendes Ergebniserzeugt:

template<class R > yes type is function tester(R (*)()); template<classR , class A0> yes type is function tester(R (*)( A0)); template<class R ,class A0 , class A1> yes type is function tester(R (*)( A0 , A1)); templa-te<class R , class A0 , class A1 , class A2> yes type is function tester(R(*)( A0 , A1 , A2)); template<class R , class A0 , class A1 , class A2 ,class A3> yes type is function tester(R (*)( A0 , A1 , A2 , A3)); templa-te<class R , class A0 , class A1 , class A2 , class A3 , class A4> yes typeis function tester(R (*)( A0 , A1 , A2 , A3 , A4)); template<class R ,

16

Page 18: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

class A0 , class A1 , class A2 , class A3 , class A4 , class A5> yes typeis function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5)); template<classR , class A0 , class A1 , class A2 , class A3 , class A4 , class A5 , classA6> yes type is function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6));template<class R , class A0 , class A1 , class A2 , class A3 , class A4, class A5 , class A6 , class A7> yes type is function tester(R (*)( A0 ,A1 , A2 , A3 , A4 , A5 , A6 , A7)); template<class R , class A0 , classA1 , class A2 , class A3 , class A4 , class A5 , class A6 , class A7 , classA8> yes type is function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6, A7 , A8)); template<class R , class A0 , class A1 , class A2 , class A3, class A4 , class A5 , class A6 , class A7 , class A8 , class A9> yes typeis function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6 , A7 , A8 , A9));template<class R , class A0 , class A1 , class A2 , class A3 , class A4 ,class A5 , class A6 , class A7 , class A8 , class A9 , class A10> yes typeis function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6 , A7 , A8 , A9 ,A10)); template<class R , class A0 , class A1 , class A2 , class A3 , classA4 , class A5 , class A6 , class A7 , class A8 , class A9 , class A10 , classA11> yes type is function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6 ,A7 , A8 , A9 , A10 , A11)); template<class R , class A0 , class A1 , classA2 , class A3 , class A4 , class A5 , class A6 , class A7 , class A8 , classA9 , class A10 , class A11 , class A12> yes type is function tester(R (*)(A0 , A1 , A2 , A3 , A4 , A5 , A6 , A7 , A8 , A9 , A10 , A11 , A12));template<class R , class A0 , class A1 , class A2 , class A3 , class A4 , classA5 , class A6 , class A7 , class A8 , class A9 , class A10 , class A11 , classA12 , class A13> yes type is function tester(R (*)( A0 , A1 , A2 , A3 , A4, A5 , A6 , A7 , A8 , A9 , A10 , A11 , A12 , A13)); template<class R , classA0 , class A1 , class A2 , class A3 , class A4 , class A5 , class A6 , class A7, class A8 , class A9 , class A10 , class A11 , class A12 , class A13 , classA14> yes type is function tester(R (*)( A0 , A1 , A2 , A3 , A4 , A5 , A6 ,A7 , A8 , A9 , A10 , A11 , A12 , A13 , A14));

Obwohl an sich beeindruckend, ergibt sich offensichtlich ein neuesProblem: die Ausgabe des Praprozessors besteht modulo anderer Ausgaben,die mit der Arbeitsweise der BPL zusammenhangen (und hier entferntwurden), aus nur einer Zeile (horizontale Wiederholung): von Lesbarkeitdes sich ergebenden Codes kann keine Rede mehr sein. Im Falle eines Fehlerswird die Ausgabe fur die Suche nach der Ursache nahezu unbrauchbar.

17

Page 19: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

4.3 Vertikale Iteration

4.3.1 Lokale Iteration

Lokale Iteration expandiert ein vom Benutzer angegebenes Makro so oftwie vom Benutzer angegeben. Jede der einzelnen Expansionen wird abernun auf einer neuen Zeile ausgefuhrt.

Lokale Iteration lasst sich in obigem Beispiel erreichen, indem man

BOOST_PP_REPEAT(

BOOST_PP_INC(

MAX_IS_FUNCTION_TESTER_PARAMS), \

IS_FUNCTION_TESTER, _)

ersetzt durch

#define BOOST_PP_LOCAL_MACRO(N) \

IS_FUNCTION_TESTER(_,N,_)

#define BOOST_PP_LOCAL_LIMITS \

(0,MAX_IS_FUNCTION_TESTER_PARAMS-1)

#include BOOST_PP_LOCAL_ITERATE()

Es ergibt sich:

template<class R> yes_type is_function_tester(R (*)());

template<class R, class A0> yes_type is_function_tester(R (*)(A0));

template<class R, class A0, class A1> yes_type is_function_tester(R (*)(A0,A1);

template<class R, class A0, class A1, class A2> yes_type is_function_tester(R (*) ....

...

...

Obwohl das bereits eine deutliche Verbesserung hinsichtlich der Lesbar-keit darstellt, haben beide bisher vorgestellten Methoden (immer noch) dengravierenden Nachteil eines aus Makros generierten Quellcodes:

Der Maschinen-Code, der sich ergibt, ist in einer spateren Debugsit-zung kaum oder nur unter großen Schwierigkeiten zu beherrschen.14

14Der Autor hatte bereits das”Vergnugen” ein mit C-Makros geschriebenes Betriebssy-

stem debuggen zu durfen.

18

Page 20: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Das Problem ist, das der Debugger als Zeileninformation nur die Zei-le hat, an der das Makro ausgewertet wurde. Jedes Statement, das durchden Aufruf des Makros erzeugt wird, tragt innerhalb des Debuggers alsodieselbe Zeilennummer.

Diese Situation lasst sich durch sogenannte Datei-Iteration weiterverbessern. Zuvor sei aber die Funktionsweise der lokalen Iteration kurzerlautert.

4.3.2 Funktionsweise der lokalen Iteration

Vom Benutzer mussen zwei Dinge angegeben werden: das zu ex-pandierende Makro BOOST PP LOCAL MACRO und der Be-reich, uber den iteriert werden soll: BOOST PP LOCAL LIMITS.

Sind diese beiden Makros angegeben, wird der Iterationsprozess durch dieInklusion des Makros BOOST PP LOCAL ITERATE() gestartet.

#define BOOST_PP_LOCAL_MACRO(N) MACRO(Z,N,_)

#define BOOST_PP_LOCAL_LIMITS (0,RANGE)

#include BOOST_PP_LOCAL_ITERATE()

// Start der Iteration

#define BOOST_PP_LOCAL_ITERATE()

<boost/preprocessor/iteration/detail/local.hpp>

// (1) ...

// ...

// Extraktion des Startindex der Iteration aus dem

// benutzerdefiniertem Makro BOOST_PP_LOCAL_LIMITS.

#define BOOST_PP_VALUE BOOST_PP_TUPLE_ELEM

(2, 0, BOOST_PP_LOCAL_LIMITS)

// (2) Berechnung des Startindex. Interne Speicherung.

#include <boost/preprocessor/iteration/detail/start.hpp>

// Extraktion des Endindex der Iteration aus dem

// benutzerdefiniertem Makro BOOST_PP_LOCAL_LIMITS.

#define BOOST_PP_VALUE BOOST_PP_TUPLE_ELEM

(2, 1, BOOST_PP_LOCAL_LIMITS)

// (3) Berechnung des Endindex. Interne Speicherung.

#include <boost/preprocessor/iteration/detail/finish.hpp>

19

Page 21: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Zur Erlauterung:Nachdem <boost/preprocessor/iteration/detail/local.hpp> vom Praprozes-sor angezogen ist (1), wird zunachst BOOST PP VALUE extrahiert:

#define BOOST_PP_VALUE BOOST_PP_TUPLE_ELEM

(2, 0, BOOST_PP_LOCAL_LIMITS)

BOOST PP VALUE wertet nun auf den Startindex (0) der Iterationaus.

Fur den nachfolgenden Schritt ist es erforderlich, sich klarzumachen,dass der Praprozessor selbst zwar arithmetische Ausdrucke auswerten kann,wenn er aber als Metaprogramm benutzt wird, eine Zahl fast immer nur alseinfaches Token interpretiert, das keinerlei Sonderbedeutung hat.

Die Bibliothek muss also z.B. das Token”0” in die Zahl 0 konvertie-

ren, was durch das Einbinden von

<boost/preprocessor/iteration/detail/start.hpp> und<boost/preprocessor/iteration/detail/finish.hpp>

geschieht ((2),(3))15.

Vor dem Einbinden von <boost/preprocessor/iteration/detail/finish.hpp>

muss BOOST PP VALUE erneut extrahiert werden, um den Endindexanalog zum Startindex berechnen zu konnen (RANGE).

Die sich ergebenden Werte werden in den Makros BOOST PP LOCAL Sbzw. BOOST PP LOCAL F festgehalten.

Fur jeden Wert zwischen BOOST PP LOCAL Sund BOOST PP LOCAL F wird nun nacheinanderBOOST PP LOCAL C mit entsprechendem Argument aufgerufen.

15Die Situation ahnelt dem Problem eines C-Programmieranfangers, der Schwierigkeitendamit hat, zwischen dem Zeichen ’0’ und der Zahl 0 zu unterscheiden.

20

Page 22: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

#if BOOST_PP_LOCAL_C(0) \

BOOST_PP_LOCAL_MACRO(0)

#endif

#if BOOST_PP_LOCAL_C(1) \

BOOST_PP_LOCAL_MACRO(1)

#endif

#if BOOST_PP_LOCAL_C(2) \

BOOST_PP_LOCAL_MACRO(2)

...

...

#if BOOST_PP_LOCAL_C(256) \

BOOST_PP_LOCAL_MACRO(256)

#endif

Wie ersichtlich wird das vom Benutzer definierte Makro einfach in einerArt

”Unrolled-Loop” so oft wie angegeben expandiert. Dieser Iterationsvor-

gang ist lokal in einer Datei gehalten, woraus sich der Name erklart.

4.3.3 Datei-Iteration

Im Unterschied zur lokalen Iteration wird bei der Datei-Iteration nichteinfach nur ein Makro expandiert, sondern es wird eine Datei wiederholteingebunden.

Die Zeileninformation innerhalb dieser Datei bleibt also auch wah-rend einer Debugsitzung erhalten.

Dieses Phanomen ist vom Debuggen von Templates her bekannt: mangewinnt den Eindruck durch die gleichen Zeilen des Quellcodes zu wandern,auch wenn verschiedene Instanzen des gleichen Templates vorliegen.

4.3.4 Funktionsweise der Datei-Iteration

Wieder sind vom Benutzer zwei Dinge anzugeben: der Iterationsbereich so-wie die Datei, die wiederholt eingebunden werden soll.16

16In der Praxis werden Dateien durch durch sogenannte”Include-Guards” aus verschie-

denen Grunden vor einer mehrfachen Einbindung geschutzt. Bei Datei-Iteration sind solche

”Guards” aus der zu inkludierenden Datei zu entfernen.

21

Page 23: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Die Iteration wird dann durch die Einbindung des MakrosBOOST PP ITERATE() in Gang gesetzt.

Im nachfolgenden Beispiel wird fur die beiden Benutzerangaben dasMakro BOOST PP ITERATION PARAMS 1 benutzt.17

BOOST PP ITERATION PARAMS 1 muss sich zu einem Arrayexpandieren lassen, das folgende Daten enthalt: unterer Bereich, obererBereich, Dateiname und optionale Flags, auf die weiter unten noch einge-gangen wird, wie auch auf die Endung

”1” des Makros.

// [sample.h start

template<> struct sample<BOOST_PP_ITERATION()> ;

// sample.h - end]

template<int> struct sample;

#define BOOST_PP_ITERATION_PARAMS_1 \

(3, (1,5,sample.h))18

#include BOOST_PP_ITERATE()

Es ist nun wenig hilfreich, eine Datei einfach mehrfach einzubinden. Wasman benotigt, ist vielmehr der Zugriff auf den Iterationszahler und diesenbekommt man uber das Makro BOOST PP ITERATION().Deshalb erzeugt obiges Beispiel folgende Ausgabe:

template<> struct sample<1> ;template<> struct sample<2> ;template<> struct sample<3> ;template<> struct sample<4> ;template<> struct sample<5> ;

17Es ist grundsatzlich moglich, dies getrennt in zwei Makros anzugegeben.BOOST PP ITERATION PARAMS 1 lasst sich jedoch auch fur optionale Parameter ver-wenden.

22

Page 24: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

4.3.5 Selbstbezugliche Iteration

Nun besteht kein Grund, warum eine Datei nicht uber sich selbst iterierenkonnte. Es muss nur eine Moglichkeit geschaffen werden, den

”normalen”

Teil der Datei von dem, uber den iteriert wird, unterscheiden zu konnen.

Die Bibliothek stellt zu diesem Zweck das MakroBOOST PP IS ITERATING bereit, welches zu

”1” auswertet, falls eine

Iteration im Gange ist. Obiges Beispiel wird damit zu:

// sample.h

#if !BOOST_PP_IS_ITERATING

#ifndef SAMPLE_H

#define SAMPLE_H

#include <boost/preprocessor/iteration/iterate.hpp>

template<int> struct sample;

#define BOOST_PP_ITERATION_PARAMS_1 \

(3, (1, 5,sample.h))

#include BOOST_PP_ITERATE()

#endif // SAMPLE_H

#else

template<> struct sample<BOOST_PP_ITERATION()> ;

#endif

Auch zwei (oder mehrere) simultane Iterationen sind so moglich:

23

Page 25: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

// sample.h

#if !BOOST_PP_IS_ITERATING

#ifndef SAMPLE_H

#define SAMPLE_H

#include <boost/preprocessor/iteration/iterate.hpp>

#include <boost/preprocessor/repetition/enum_params.hpp>

#include <boost/preprocessor/repetition/enum_shifted_params.hpp>

template<int> struct sample;

#define BOOST_PP_ITERATION_PARAMS_1 (4, (1, 5,sample.h, 1))

#include BOOST_PP_ITERATE()

template<class T, class U> struct typelist_t {

typedef T head;

typedef U tail;

};

template<int> struct typelist;

struct null_t;

template<> struct typelist<1> {

template<class T0> struct args {

typedef typelist_t<T0, null_t> type;

};

};

#ifndef TYPELIST_MAX

#define TYPELIST_MAX 50

#endif

#define BOOST_PP_ITERATION_PARAMS_1 \

(4, (2, TYPELIST_MAX,sample.h, 2))

#include BOOST_PP_ITERATE()

#endif // SAMPLE_H

#elif BOOST_PP_ITERATION_FLAGS() == 1

template<> struct sample<BOOST_PP_ITERATION()> {};

#elif BOOST_PP_ITERATION_FLAGS() == 2

#define N BOOST_PP_ITERATION()

template<> struct typelist<N> {

template<BOOST_PP_ENM_PARAMS(N, class T)> struct args {

typedef typelist_t<

T0,

typename typelist<N-1>::args<

BOOST_PP_ENUM_SHIFTED_PARAMS(N,T)>::type> type;

};

};

#undef N

#endif

24

Page 26: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Die Bibliothek stellt zu diesem Zweck optionale Flags zur Verfugung, dieinnerhalb der Makros BOOST PP ITERATION PARAMS x gesetztwerden konnen. Abgefragt werden diese Flags dann mittelsBOOST PP ITERATION FLAGS().

4.3.6 Mehrdimensionale Iteration

Es ist moglich uber mehrere Dimensionen zu iterieren, was in der Funktio-nalitat verschachtelten Schleifen entspricht. Die erste Dimension (d.h. dieerste Schleifenebene) wurde bereits oben benutzt.

Fur die zweite Dimension hat man in den folgenden Makros denPlatzhalter 1 durch 2 zu ersetzen (analog fur jede weitere):

#define BOOST_PP_ITERATION_PARAMS_2 /* .... */

#define BOOST_PP_FILENAME_2 /* .... */

// file.h

#if !BOOST_PP_IS_ITERATING

#ifndef FILE_H

#define FILE_H

#include <boost/preprocessor/iteration/iterate.hpp>

#define BOOST_PP_ITERATION_PARAMS_1 (3, (1, 2,file.h))

#include BOOST_PP_ITERATE()

#endif // FILE_H

#elif BOOST_PP_ITERATION_DEPTH() == 1

+ BOOST_PP_ITERATION()

#define BOOST_PP_ITERATION_PARAMS_2 (3, (1, 2,file.h))

#include BOOST_PP_ITERATE()

#elif BOOST_PP_ITERATION_DEPTH() == 2

- BOOST_PP_ITERATION()

#endif

25

Page 27: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

Obiges Beispiel bedarf einiger Erlauterungen.

Das Makro BOOST PP ITERATION() liefert expandiert immerden Iterationszahler der aktuellen Dimension, welche wiederum im MakroBOOST PP ITERATION DEPTH() kodiert ist.

BOOST PP ITERATION PARAMS 1 (3, (1, 2, “file.h“))legt fest, dass die Datei

”file.h” zweimal inkludiert werden soll.

Wie bereits gesehen, wird die Iteration durch das Einbin-den von BOOST PP ITERATE() gestartet. Intern wirddabei BOOST PP ITERATION DEPTH() jeweils um1 erhoht. Bei Ruckkehr zum ubergeordneten Level wirdBOOST PP ITERATION DEPTH() dann entsprechend zuruck-gesetzt.

Die Ausgabe des obigen Testlauf ergibt sich somit zu:

+ 1 // Start- 1 // Dimension 2, 1.Iteration- 2 // Dimension 2, 2.Iteration+ 2 // Rueckkehr zu Dimension 1, 2.Iteration- 1 // Dimension 2, 1.Iteration- 2 // Dimension 2, 2.Iteration

Die”Boost Preprocessor Library” (BPL) enthalt neben den

vorgestellten Techniken noch viele andere, die aus Platz- und Zeitgrundenhier nicht vorgestellt werden konnen. Dazu gehoren z.B. alle Arten vonKontrollstrukturen, Listen und Aufzahlungen.

Die Dokumenation der Bibliothek [14] ist leider nicht selbsterklarend,[6] bietet jedoch eine gute Einfuhrung. Fur ein tiefergehendes Verstandnisist man aber auf das Lesen der entsprechenden Quellcodes angewiesen.

Die besondere Bedeutung der Bibliothek liegt insbesondere auch dar-in, dass sie von den anderen Bibliotheken der Boost-Library-Suite starkbenutzt wird. Zum Studium der Quellen der Boost-Bibliotheken ist also einsolides Verstandnis der BPL unerlasslich.

26

Page 28: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

5 Nachteile der Metaprogrammierung in C++

Abschließend soll auch auf die Nachteile der Metaprogrammierung mit C++hingewiesen werden:

• Compilerbeschrankungen

Obwohl dieses Problem an Bedeutung verliert, ist es immer noch so,dass gewisse Compiler bei der Metaprogrammierung mit Templates anihre Grenzen geraten. Das bedeutet naturlich auch, dass es mitunterschwierig sein kann, Metacode zu portieren.

• Hohe Anforderungen an die Rechenleistung wahrend derKompilierphase

Will man Metaprogrammierung mit Templates betreiben”

beno-tigt man einen Rechner mit hoher Rechenleistung und

”Java-sized”

Speicherbestuckung.

• Geringe Lesbarkeit des Codes

Die Lesbarkeit des Template Metacodes ist nicht sehr hoch. DerGrund hierfur liegt auf der Hand: Metaprogrammierung war in C++nie ein Ziel der Sprach-Designer.

• Debuggen des Meta-Codes

Das Debuggen von Template-Metaprogrammen ist sehr schwie-rig, wenn nicht fast unmoglich. Es existiert eben kein Debugger furden Kompiliervorgang selbst. Außerdem kann die Anzahl der Zeicheneiner einzigen Variable leicht die 10.000 uberschreiten, so dass dasLesen selbst von Fehlermeldungen schnell zu einem Alptraum werdenkann.

• Nachteile des Praprozessors

Setzt man den Praprozessor zur Metaprogrammierung ein, sosind dessen Nachteile bekannt: Nebeneffekte, geringe Lesbarkeit und

”Namespace-Pollution” durch eine große Anzahl von Makros. Diese

Nachteile werden durch den Einsatz der BPL zwar gemildert, abernur solange die Spezifikation der BPL beachtet wird.

Literatur

[1] K. Kreft A. Langer. C++ Expression Templates. C/C++ Users Jour-nal, 2003.

27

Page 29: C++ - Metaprogrammierung · C++ - Metaprogrammierung Hoffmann Gerhard Blockseminar am 23.06.2005: Fortgeschrittene Konzepte in C++ Zusammenfassung Generative Programmierung hat zum

[2] A. Alexandrescu. Generic<Programming>: Move Constructors. Ex-perts Forum, <www.cuj.com/experts/2102/alexandr.htm>. C/C++Users Journal, Feb. 2003.

[3] T. Becker. C++ Template Metaprogramming Applied. C/C++ UsersJournal, 2003.

[4] T. Becker. Creating efficient code with templates. C/C++ Users Jour-nal, 2003.

[5] T. Becker. Expression Templates. C/C++ Users Journal, 2003.

[6] A.Gurtovoy D. Abrahams. C++ Template Metaprogramming. C++In-Depth Series. Addison-Wesley Professional, 2005.

[7] N.M. Josuttis D. Vandevoorde. C++ Templates. Addison-Wesley Pro-fessional, 2003.

[8] C. Henderson. String Concatenation and Expression Templates.C/C++ Users Journal, 2005.

[9] U.Eisenecker K. Czarnecki. Generative Programming. Addison-WesleyPearson Education, 2000.

[10] Erwin Unruh. Prime number computation. ANSI X3J16-94-0075/ISOWG21-462, 1994.

[11] Todd Veldhuizen. Blitz++. http://www.oonumerics/blitz/.

[12] Todd Veldhuizen. Using C++ Template Metaprogramms. C++ Report,SIGS Publications Inc., ISSN 1040-6042, Vol. 7, No. 4, pp. 36-43, May1995, 1995.

[13] www.boost.org. The Boost Metaprogramming Library,http://www.boost.org/libs/mpl/doc/index.html. 2002.

[14] www.boost.org. The Boost Preprocessor Library,http://www.boost.org/libs/preprocessor/doc/index.html. 2002.

28