C++-Templates sind ihrem Wesen nach so konzipiert, dass sie am besten funktionieren, wenn jeder Client das gesamte Template sieht – einschließlich der Prozedurrümpfe. Die Idee dahinter ist die, dass der Compiler entscheidet, welche Instanziierungen erfolgen müssen. Genau so werden die STL-Templates verwendet. Das kann jedoch eine hohe Last für den Compiler bedeuten, und die Kompilierung in bestimmten Situationen verlangsamen. Deshalb ist es mitunter von Vorteil, Template-Deklarationen und -Definitionen so zu organisieren, dass ihre separate Kompilierung möglich ist.
Bei GrammaTech begegnet uns diese Situation bei der Analyse von Maschinencode in unserem TSL-System. Für jede Architektur (z. B. IA32, PPC) generieren wir eine Template-Klasse, die Methoden enthält, die die Semantik dieser Architektur beschreiben. Diese Templates werden mit Analysespezifikationen instanziiert (z. B. use-def-Analyse, affine-relations-Analyse), um Implementierungen von use-def-Analysen für IA32, use-def-Analysen für PPC, affine-relations-Analysen für IA32, usw. zu erhalten. Wie Sie sich vorstellen können, sind diese Template-Klassen sehr umfangreich und enthalten ihrerseits sehr umfangreiche Methoden. Zudem müssen Analysen in der Lage sein, per Template-Spezialisierung einen Teil der Semantikklassenmethoden zu spezialisieren. Aus dem Wesen dieser Template ergeben sich zwei große Probleme:
(1) Wurden die großen KIassenmethoden-Definitionen in eine Header-Datei eingeschlossen, die jeder sieht, dauerte die Kompilierung sehr lange (hin und wieder ist es uns gelungen, den Compiler zu überlasten).
(2) Weil die Template-Spezialisierung eine schwierige Kunst ist, machten wir oft den Fehler, die Deklarationen nicht an die richtige Stelle zu setzen – ein Problem, das noch durch den Umstand verschärft wird, dass Compiler sich nicht beschweren, wenn Sie hier nicht sauber arbeitet, und dass unterschiedliche Compiler unterschiedlich reagieren, wenn man passiert, mitunter bizarr und unerwartet.
Zur Optimierung dieses Codes entwickelten wir ein Template (wenn man so will), um derartige Templates in einer Kollektion mit Header-Dateien zu organisieren. Das stellt Abbildung 1 mit Pfeilen auf #include-Anweisungen dar. Zu beachten ist, dass es eine Regel gibt, die das Einschließen (#include) von cpp-Dateien untersagt – wenn also etwas von einer anderen Datei einzuschließen ist, auch wenn es wie eine cpp-Datei aussieht, muss es die Namenserweiterung „hpp“ erhalten. Diese Regel bedeutet auch, dass ausschließlich cpp-Dateien eine Kompilierungseinheit bilden.

Dieses Layout ist für den Fall vorgesehen, dass Methodenrümpfe (C) groß sind – etwa, wenn die Anzahl der Kompilierungseinheiten, die diese sehen, minimiert werden soll – und für den Fall, dass es relativ wenige Instanziierungen (F) gibt. Bei diesem Layout werden die großen Methodenrümpfe in (C) nur einmal pro Instanziierung (F) kompiliert. Bei Templates im STL-Stil beispielsweise, wo die Anzahl der Instanziierungen in der Regel beliebig groß ist, würde dieses Layout nicht gut funktionieren, weil es die Erzeugung einer instantiator.cpp-Datei für jede Instanziierung erfordert (z. B. eine für set<int>, eine für set<unsigned int>, eine für set<foo>, … eine für jede Verwendung von „set“ im betreffenden Projekt).
Wichtig ist auch, dass gegebenenfalls mehrere Kopien von (C) und (F) möglich sind. Bei GrammaTech könnte Foo beispielsweise IA32Semantics sein, durch dessen viele große Methodenrümpfe die Kompilierung jedes Instantiators (F) ziemlich langsam erfolgt. Man könnte (C) stattdessen in mehrere Dateien aufsplitten (z. B. eine pro Methode), und (F) entsprechend auch in mehrere Dateien aufteilen. Das Splitten von (F) könnte ein bisschen beschwerlich werden, weil die Template-Methoden einzeln instanziiert werden müssen. Es ist ein möglicher Kompromiss, um die Kompilierungszeiten zu verbessern.
Diese Gestaltung schließt Unterstützung für die Methodenspezialisierung (E) ein. Ihre Verwendung ist jedoch an Vorbehalte geknüpft. Wenn beispielsweise der Rumpf von func2 in (C) func1 aufruft, kann es erforderlich sein, dass (F) (E) einschließt, bevor (C) eingeschlossen wird – d. h., beim Kompilieren von func2 muss die Spezialisierung von func1 in (E) deklariert worden sein. In diesem Fall ist Folgendes zu berücksichtigen: Es bedeutet, dass die Inline-Methoden in (B) u. U. keine spezialisierten Methoden aufrufen – und der Template Designer ((A), (B), (C)) vorher nicht weiß, welche Methoden spezialisiert werden könnten. Um dem Rechnung zu tragen, könnte man das Bild durch Aufsplitten von (B) in zwei Dateien verfeinern – oder der Einfachheit halber Inline-Methoden einfach verbieten.
Leider lässt sich der sachgemäße Einsatz dieses „Templates“ nicht einfach mit einem Tool erzwingen oder validieren – das setzt Disziplin, profundes Wissen und Unfehlbarkeit voraus – Attribute, die – das müssen selbst die besten Programmierer einräumen – nicht zu garantieren sind. Dennoch hoffe ich, dass andere dieses Template hilfreich finden. Indes warte auf stabile Implementierungen der externen Templates von C++11, auch wenn ich nicht glaube, dass diese Gestaltung dadurch überflüssig wird.