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.
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).
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).







