Open Source im professionellen Einsatz
Linux-Magazin 01/2011
© Juri Samsonov, 123RF.com

© Juri Samsonov, 123RF.com

Template Metaprogramming mit C++

Magischer Mechanismus

C++-Programme arbeiten zur Laufzeit, der eingebaute Template-Mechanismus aber zur Compilezeit. Geschickte Entwickler nutzen dies für Metaprogrammierung im funktionalen Stil, die erstaunlich performante und sogar selbstoptimierende Software ermöglicht.

1081

Obwohl durch Zufall entdeckt, erweitert Template Metaprogramming die Sprache C++ mit einer Mächtigkeit, die sich mit klassischem Programmcode nicht erreichen lässt. Der Template-Mechanismus, ursprünglich zum generischen Programmieren (unabhängig vom Datentyp) eingeführt, entpuppte sich als Metasprache in C++, die Typen und Werte zur Compilezeit berechnet. Die resultierenden Programme sind schneller, da sich die Arbeit von der Laufzeit auf die Compilezeit verlagert. Höhere Performance paart sich mit erweiterter Funktionalität.

Wie alles begann

1994 stellte Erwin Unruh von Siemens-Nixdorf dem C++-Standardkomitee sein Primzahlen-Programm vor – das wohl berühmteste C++-Programm, das nicht kompiliert [1]. Die Fehlermeldung als Seiteneffekt des Übersetzungsprozesses war das Revolutionäre seines Programms: Die Primzahlen bis 30 waren in den Fehlerausgaben des Compilers versteckt. Listing 1 zeigt die wesentlichen Zeilen der originalen Ausgabe.

Listing 1

Fehlerausgabe des Primzahlen-Programms

01 |    Type `enum{}' can't be converted to txpe `D<2>' ("primes.cpp",L2/C25).
02 |    Type `enum{}' can't be converted to txpe `D<3>' ("primes.cpp",L2/C25).
03 |    Type `enum{}' can't be converted to txpe `D<5>' ("primes.cpp",L2/C25).
04 |    Type `enum{}' can't be converted to txpe `D<7>' ("primes.cpp",L2/C25).
05 |    Type `enum{}' can't be converted to txpe `D<11>' ("primes.cpp",L2/C25).
06 |    Type `enum{}' can't be converted to txpe `D<13>' ("primes.cpp",L2/C25).
07 |    Type `enum{}' can't be converted to txpe `D<17>' ("primes.cpp",L2/C25).
08 |    Type `enum{}' can't be converted to txpe `D<19>' ("primes.cpp",L2/C25).
09 |    Type `enum{}' can't be converted to txpe `D<23>' ("primes.cpp",L2/C25).
10 |    Type `enum{}' can't be converted to txpe `D<29>' ("primes.cpp",L2/C25).

Der Beweis war erbracht: Template-Instanziierung lässt sich dazu verwenden, Werte zur Compilezeit zu berechnen. Das Programm in Listing 2 beispielsweise rechnet die Fakultät von 5 zur Compilezeit aus. Das primäre oder auch allgemeine Template »template <int N> struct Factorial« ruft sich mit »static int const value= N * Factorial<N-1>::value« rekursiv selbst auf, bis die Template-Spezialisierung »template <> struct Factorial<1>« eintritt. In diesem Fall wird die Rekursion mit dem Wert 1 beendet und das Ergebnis berechnet.

Listing 2

Fakultät-Berechnung zur Compilezeit

01 #include <iostream>
02
03 template <int N>
04 struct Factorial{
05     static int const value= N * Factorial<N-1>::value;
06 };
07
08 template <>
09 struct Factorial<1>{
10     static int const value = 1;
11 };
12
13 int main(){
14     std::cout << Factorial<5>::value << std::endl;
15     std::cout << 120 << std::endl;
16     return 0;
17 }

Da die Template-Instanziierung und somit auch die Rekursion zur Compilezeit abläuft, liegt der Wert von »Factorial <5>::value« zur Laufzeit des resultierenden Programms als Konstante vor. Das Übersetzen des Quellcode mit Debug-Informationen per »g++ -g -o fakultaet fakultaet.cpp« und ein anschließender Objektdump mit »objdump -S fakultaet« bringen die Magie in doppelter Hinsicht ans Tageslicht (Abbildung 1).

Abbildung 1: Der Objektdump des Fakultät-Programms enthält das Ergebnis der Berechnung.

Zum einen lautet die Fakultät von 5 in hexadezimaler Schreibweise »x78« . Genau dieser Wert steht in der Assembler-Anweisung »mov $0x78,$esi« . Zum anderen resultiert aus dem Sourcecode »std::cout << Factorial<5>::value << std::endl« der gleiche Assembler-Code wie aus dem Sourcecode »std::cout << 120 << std::endl« , in dem der Wert der Fakultät von 5 schon als Konstante vorliegt.

Charakteristiken

Template Metaprogramming ist eine Meta-Technik in C++, in der der Compiler die Templates zur Compilezeit interpretiert und sie in C++-Quelltext transformiert. Diesen temporär erzeugten Code übersetzt er zusammen mit dem restlichen Sourcecode. C++ ist insofern eine Zwei-Level-Sprache, weil der statische Code zur Compilezeit, der resultierende dynamische Code aber zur Laufzeit ausgeführt wird (Abbildung 2).

Abbildung 2: Dank des Template-Mechanismus wird C++ zu einer Zwei-Level-Sprache.

Mit Template Metaprogramming lassen sich aber nicht nur Werte berechnen, das Programm in Listing 3 verändert Datentypen zur Compilezeit. Es entfernt die Konstantheit (Constness) der Typen. Das Programm demonstriert die Charakteristiken des Template Metaprogramming: Der Quelltext besteht aus dem primären Klassentemplate »template <typename T> struct RemoveConst« und dessen Spezialisierung »template <typename T>struct RemoveConst<const T>« , die nur konstante Typen annimmt.

Listing 3

Das Klassentemplate RemoveConst

01 #include <iostream>
02 #include <map>
03
04 template <typename T>
05 struct RemoveConst{
06     typedef T type;
07 };
08
09 template <typename T>
10 struct RemoveConst<const T>{
11     typedef T type;
12 };
13
14 template <typename T>
15 void checkConst(){
16     if (std::is_const<T>::value == true  ) {
17         std::cout << "const " << std::endl;
18     }
19     else {
20         std::cout << "not const" << std::endl;
21     }
22 }
23
24 int main(){
25
26   std::cout << "int : "; checkConst<int>();
27   std::cout << "const int: "; checkConst<const int>();
28   std::cout << "RemoveConst<int>::type > "; checkConst< RemoveConst<int>::type >();
29   std::cout << "RemoveConst<const int>::type > : "; checkConst< RemoveConst<const int>::type >();
30   typedef RemoveConst<const int>::type NotConstFromConstInt;
31   std::cout << "NotConstFromConstInt : "; checkConst< NotConstFromConstInt >();
32   NotConstFromConstInt i= 10;
33
34 }

Während das primäre Template den Datentyp unverändert zurückgibt, liefert die Spezialisierung für konstante Datentypen diese nicht-konstant zurück. Das Funktionstemplate »template <typename T> void checkConst()« ist nur eine Hilfsfunktion, die ausgibt, ob der Templateparameter »T« konstant ist. Dies ermittelt die Funktion »std::is_const<T>::value« aus dem kommenden C++0x-Standard [2] zur Compilezeit.

Abbildung 3: Dieses Programm operiert auf Typen: Es nimmt konstante Datentypen entgegen und gibt sie nicht-konstant zurück.

Die letzten vier Zeilen der Main-Funktion sind die interessantesten im ganzen Listing. In Zeile 29 nimmt der Ausdruck »RemoveConst<const int>::type« einen »const int« als Eingabe und gibt einen »int« zurück. Der Ausdruck »typedef RemoveConst<const int>::type NotConstFromConstInt« erklärt ein Alias auf diesen Typ. Dieses Alias ist nichts anderes als ein Integer und lässt sich auch im weiteren Programm (Zeile 32) als solcher verwenden.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 7 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++11

    C++-Code sicherer machen und zugleich an der Performance-Schraube drehen, das sind die beiden Domänen der neuen Type-Traits-Bibliothek. Sie beschleunigt Code, indem sie Typen zur Kompilierzeit analysiert und verändert, wenn der Entwickler sie geschickt einsetzt.

  • C++

    Gut 30 Artikel und fünf Jahre ist diese Serie zum modernen C++ nun alt. Jetzt ist es Zeit für einen Fokuswechsel. Drehte es sich bisher um die Frage "Welche Features bietet modernes C++?", so wird es in Zukunft um die Frage gehen: "Wie setzt der Programmierer die Features von modernem C++ richtig ein?"

  • Ruby on Rails 4.1.0 bringt neuen Preloader

    Das Ruby-Webframework Rails ist in Version 4.1.0 erschienen. Trotz des kleinen Versionssprungs gibt es nützliche Neuerungen und Verbesserungen.

  • Erfrischend neu

    An der Sprache C++ ändert sich selten etwas. Doch nun steht mit dem C++0x ein neuer Standard vor der Tür, von dem Neulinge wie Könner profitieren sollen. Mit Move-Semantik, Lambda-Funktionen und Aggregatzuweisungen bringt er viele Modernisierungen in die Programmiersprache.

  • C++

    Implizite Typkonvertierungen sind eine der Ursachen für undefiniertes Verhalten in C- und C++-Programmen. In Kurzform bedeutet dies, dass solche Programme alles Mögliche tun dürfen und unvorhersehbar reagieren. C++-Routinier Rainer Grimm zeigt, wie C++-Entwickler diese Falle umgehen.

comments powered by Disqus

Ausgabe 09/2017

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.