Templates sind ein Merkmal der Programmiersprache C++ , das es Funktionen und Klassen ermöglicht, mit generischen Typen zu arbeiten . Dadurch kann eine Funktions- oder Klassendeklaration über eine generische Variable auf eine andere Klasse (eingebauter oder neu deklarierter Datentyp ) verweisen, ohne für jede dieser Klassen eine vollständige Deklaration erstellen zu müssen.
Vereinfacht ausgedrückt entspricht eine Template-Klasse oder -Funktion dem Kopieren und Einfügen des Template-Codeblocks an der entsprechenden Stelle (vor der Kompilierung) und dem anschließenden Ersetzen des Template-Parameters durch den tatsächlichen Wert. Aus diesem Grund platzieren Klassen mit Template-Methoden die Implementierung in den Headerdateien ( *.h -Dateien), da kein Symbol ohne vorherige Kenntnis seines Typs kompiliert werden kann.
Die C++-Standardbibliothek bietet viele nützliche Funktionen innerhalb eines Rahmens aus verbundenen Templates.
Wesentliche Inspirationen für C++-Templates lieferten die parametrisierten Module der Sprach- CLU und die Generics von Ada . [ 1 ]
Technischer Überblick
Es gibt drei Arten von Templates: Funktionstemplates , Klassentemplates und, seit C++14 , Variablentemplates . Seit C++11 können Templates entweder variadisch oder nicht-variadisch sein; in früheren C++-Versionen waren sie immer nicht-variadisch.
C++ Templates sind Turing-vollständig . [ 2 ]
Funktionsvorlagen
Eine Funktionsvorlage verhält sich wie eine Funktion, mit dem Unterschied, dass sie Argumente verschiedener Typen akzeptieren kann und somit typgenerisches Verhalten ermöglicht (siehe Beispiel). Anders ausgedrückt: Eine Funktionsvorlage repräsentiert eine Familie von Funktionen. Die Formatierung von Funktionsvorlagen mit Typparametern lautet:
template < class Identifier > Deklaration ; template < typename Identifier > Deklaration ;Beide Ausdrücke haben dieselbe Bedeutung und verhalten sich exakt gleich. Die letztere Form wurde eingeführt, um Verwechslungen zu vermeiden [ 3 ], da ein Typparameter bis C++20 keine Klasse sein musste. (Es konnte ein Basistyp wie z. intB. oder sein double.)
Die C++-Standardbibliothek enthält beispielsweise die Funktionsvorlage , die den größeren Wert von und max(x, y)zurückgibt . Diese Funktionsvorlage könnte wie folgt definiert werden [ 4 ] : xy
template < typename T > [[ nodiscard ]] constexpr T & max ( const T & a , const T & b ) noexcept { return a < b ? b : a ; }Diese einzelne Funktionsdefinition funktioniert mit vielen Datentypen. Genauer gesagt, funktioniert sie mit allen Datentypen, für die der Kleiner-als-Operator <bool definiert ist, und gibt einen Wert zurück, dessen Typ in den entsprechenden Datentyp konvertierbar ist. Die Verwendung einer Funktionsvorlage spart Speicherplatz in der Quellcodedatei, beschränkt Änderungen auf eine einzige Funktionsbeschreibung und verbessert die Lesbarkeit des Codes.
Eine instanziierte Funktionsvorlage erzeugt in der Regel denselben Objektcode, im Gegensatz zum Schreiben separater Funktionen für alle im Programm verwendeten Datentypen. Verwendet ein Programm beispielsweise sowohl eine `a`- intals auch eine `b`- doubleVersion der max()obigen Funktionsvorlage, erstellt der Compiler eine Objektcodeversion von ` max()a`, die mit `a`-Argumenten arbeitet int, und eine weitere Objektcodeversion, die mit `b`-Argumenten arbeitet double. Die Compilerausgabe ist identisch mit der Ausgabe, die erzeugt worden wäre, wenn der Quellcode zwei separate, nicht in der Vorlage enthaltene Versionen von `a` enthalten hätte – max()eine für `a`-Argumente intund eine für `b`-Argumente double.
So könnte die Funktionsvorlage verwendet werden:
import std ;int main () { // Dies ruft max<int> durch implizite Argumentableitung auf. std :: println ( "{}" , std :: max ( 3 , 7 ));// Dies ruft max<double> durch implizite Argumentableitung auf. std :: println ( "{}" , std :: max ( 3.0 , 7.0 ));// Wir müssen den Typ der Argumente explizit angeben; // obwohl std::type_identity dieses Problem lösen könnte... std :: println ( "{}" , max <double> ( 3 , 7.0 ) ); }In den ersten beiden Fällen Tleitet der Compiler das Template-Argument automatisch als `T` intbzw. `T` ab double. Im dritten Fall max(3, 7.0)würde die automatische Ableitung von `T` fehlschlagen, da der Typ der Parameter im Allgemeinen exakt mit den Template-Argumenten übereinstimmen muss. Daher instanziieren wir die doubleVersion explizit mit `T` max<double>().
Diese Funktionsvorlage kann mit jedem kopierkonstruierbaren Typ instanziiert werden, für den der Ausdruck y < xgültig ist. Für benutzerdefinierte Typen bedeutet dies, dass der Kleiner-als-Operator ( <) im Typ überladen werden muss.
Abgekürzte Funktionsvorlagen
Seit C++20 wird eine Funktionsdeklarationauto , die `or` oder `is` concept autoin einem ihrer Parameter verwendet , zu einer abgekürzten Funktionstemplate- Deklaration. [ 5 ] Eine solche Deklaration deklariert ein Funktionstemplate, und für jeden Platzhalter wird ein erfundener Template-Parameter an die Template-Parameterliste angehängt.
// entspricht: // template <typename T> // void f1(T x); void f1 ( auto x );// äquivalent zu (falls Concept1 ein Konzept ist): // <Concept1 T> // void f2(T x); void f2 ( Concept1 auto x );// entspricht (falls Concept2 ein Konzept ist): // template <Concept2... Ts> // void f3(Ts... xs) void f3 ( Concept2 auto ... xs );// entspricht (falls Concept2 ein Konzept ist): // template <Concept2 T> // void f4(T... xs); void f4 ( Concept2 auto xs , ...);// äquivalent zu (falls Concept3 und Concept4 Konzepte sind): // template <Concept3 T, Concept4 U> // f5(const T* t, U& u); void f5 ( const Concept3 auto * t , Concept4 auto & u );Die Einschränkung der max()verwendeten Konzepte könnte etwa so aussehen:
using std :: total_ordered ;// in der Typnamensdeklaration: template < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// in der requires-Klausel: template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }Klassenvorlagen
Eine Klassenvorlage liefert eine Spezifikation zur Generierung von Klassen anhand von Parametern. Klassenvorlagen werden im Allgemeinen zur Implementierung von Containern verwendet . Eine Klassenvorlage wird instanziiert, indem ihr eine bestimmte Menge von Typen als Vorlagenargumente übergeben wird. [ 6 ] Die C++-Standardbibliothek enthält viele Klassenvorlagen, insbesondere die von der Standard Template Library (STL) adaptierten Container , wie z. B. vector.
Variable Vorlagen
In C++14 können Templates auch für Variablen verwendet werden, wie im folgenden Beispiel:
template < typename T > constexpr T PI = T { 3.141592653589793238462643383L }; // (Fast) aus std::numbers::piNicht-Typ-Template-Parameter
Obwohl die Verwendung von Template-Code für Typen, wie in den obigen Beispielen, die gebräuchlichste Form des Template-Codes in C++ ist, ist es auch möglich, Template-Code für Werte zu verwenden. So kann beispielsweise eine Klasse mit folgendem Code deklariert werden:
template < int K > class MyClass ;kann mit einem spezifischen Instanziierungsparameter instanziiert werden int.
Ein praktisches Beispiel: Der Standardbibliotheks -Typ für Arrays fester Größe std::arraybasiert sowohl auf einem Typ (der den Typ des Objekts repräsentiert, das das Array enthält) als auch auf einer Zahl vom Typ std::size_t(die die Anzahl der Elemente im Array repräsentiert). Um eine Klasse zu erstellen , Arraydie dem Typ entspricht std::array, kann sie wie folgt deklariert werden:
template < class T , size_t N > struct Array ;und es könnte ein Array aus sechs chars deklariert werden:
Array < char , 6 > myArray ;Template-Spezialisierung
Wenn eine Funktion oder Klasse aus einer Vorlage instanziiert wird, erstellt der Compiler eine Spezialisierung dieser Vorlage für die verwendeten Argumente. Diese Spezialisierung wird als generierte Spezialisierung bezeichnet.
Explizite Template-Spezialisierung
Manchmal entscheidet sich der Programmierer, eine spezielle Version einer Funktion (oder Klasse) für eine bestimmte Menge von Template-Typargumenten zu implementieren. Dies wird als explizite Spezialisierung bezeichnet. Auf diese Weise können bestimmte Template-Typen eine spezialisierte Implementierung erhalten, die für den jeweiligen Typ optimiert ist oder eine aussagekräftigere Implementierung als die generische Implementierung bietet.
- Wenn eine Klassenvorlage durch eine Teilmenge ihrer Parameter spezialisiert wird, spricht man von partieller Vorlagenspezialisierung (Funktionsvorlagen können nicht partiell spezialisiert werden).
- Wenn alle Parameter spezialisiert sind, spricht man von einer vollständigen Spezialisierung .
Explizite Spezialisierung wird verwendet, wenn das Verhalten einer Funktion oder Klasse für bestimmte Parameterwerte der Vorlage vom generischen Verhalten abweichen soll, also vom Code, der von der Hauptvorlage oder den Hauptvorlagen generiert wird. Die folgende Vorlagendefinition definiert beispielsweise eine spezifische Implementierung für max()Argumente vom Typ const char*:
import std ;template <> [[ nodiscard ]] constexpr const char * max ( const char * a , const char * b ) noexcept { // Normalerweise ist das Ergebnis eines direkten Vergleichs // zweier C-Strings undefiniertes Verhalten; // die Verwendung von std::strcmp macht es definiert. return std :: strcmp ( a , b ) > 0 ? a : b ; }Variadische Templates
Mit C++11 wurden variadische Templates eingeführt , die eine variable Anzahl von Argumenten entgegennehmen können, ähnlich wie variadische Funktionen wie beispielsweise std::printf.
using std :: format_string ; using std :: ofstream ;enum class Level { ... };ofstream logFile { "logfile.txt" };template < typename ... Args > void log ( const format_string < Args ... >& fmt , Args && ... args ) { std :: println ( logFile , fmt , args ...); }Da in C++ nur variadische Parameter im C-Stil unterstützt werden, ist die einzige Möglichkeit, typsichere variadische Funktionen (wie in Java ) zu erhalten, die Verwendung von variadischen Templates.
Vorlagenaliase
Mit C++11 wurden Template-Aliase eingeführt, die wie parametrisierte typedefs funktionieren .
Der folgende Code zeigt die Umbenennung std::mapvon `to` TreeMapund std::unordered_map`to` HashMapsowie die Erstellung eines Alias StringHashMapfür std::unordered_map<K, std::string>`to`. Dies ermöglicht beispielsweise StringHashMap<int>die Verwendung von `to` als Kurzform für `to` std::unordered_map<int, std::string>.
using String = std :: string ;// Ermöglicht die optionale Spezialisierung von Hashfunktionen, Allokatoren usw. template < typename K , typename V , typename Compare = std :: less < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using TreeMap = std :: map < K , V , Compare , Alloc > ;template < typename K , typename V , typename HashFn = std :: hash < K > , typename KeyEq = std :: equal_to < K > , typename Alloc = std :: allocator < std :: pair < const K , T >> > using HashMap = std :: unordered_map < K , V , HashFn , KeyEq , Alloc > ;// oder, wenn nur K und V spezialisiert werden dürfen: template < typename K , typename V > using TreeMap = std :: map < K , V > ;template < typename K , typename V > using HashMap = std :: unordered_map < K , V > ;// Definiere StringHashMap<K> = HashMap<K, String> template < typename K > using StringHashMap = HashMap < K , String > ;StringHashMap <int> myMap = /* etwas hier... * / ;Eingeschränkte Vorlagen
Seit C++20 lassen sich Templates ähnlich wie generische Wildcards in Java oder C# und Rust- Klauseln einschränken where. Dies geschieht mithilfe von Konzepten , die eine Menge boolescher Prädikate repräsentieren, die zur Kompilierzeit ausgewertet werden.
Dieser Code definiert beispielsweise ein Konzept, das eine obere Grenze für die Vererbung darstellt. Eine Klasse erfüllt dieses Konzept, wenn sie von erbt Player, und Klassen, die dies nicht tun, können nicht als Template-Parameter in verwendet werden processListOfPlayers().
import std ;using std :: is_base_of_v ; using std :: vector ;class Player { // ... };template < typename T > concept ExtendsPlayer = is_base_of_v < Player , T > ;// T muss ein Typ sein, dessen obere Vererbungsgrenze Player ist, // wodurch alle Typen blockiert werden, die nicht von Player erben template < ExtendsPlayer T > void processListOfPlayers ( vector < T > players ) { // ... }Exportierte Vorlagen
In C++03 wurden „exportierte Templates“ in C++ eingeführt. [ 7 ] Diese wurden später in C++11 wieder entfernt , da nur sehr wenige Compiler diese Funktion tatsächlich unterstützten. [ 8 ] Der einzige bekannte Compiler, der exportierte Templates unterstützte, war Comeau C/C++ . Zu den genannten Gründen für die Entfernung zählten:
- Teuer oder komplex in der Umsetzung
- Für die meisten Nutzer bietet es wenig Nutzen und weckt auch wenig Interesse.
- Schwierig zu bedienen
- Veränderungen der Bedeutung bestehender sprachlicher Merkmale
- Einschränkung der zukünftigen Entwicklung von C++
Ein „exportiertes Template“ ist im Wesentlichen ein Klassentemplate, dessen statische Datenmember und nicht-inline Methoden exportiert werden. Es muss mit dem Schlüsselwort `exported` gekennzeichnet werden export. Ein „exportiertes Template“ zeichnet sich dadurch aus, dass es nicht in einer Übersetzungseinheit definiert werden muss , die das Template verwendet. [ 9 ] [ 10 ] Zum Beispiel (in C++03):
Datei1.cpp :
#include <iostream>static void trace () { std :: cout << "Datei 1" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y );int main () { trace (); std :: cout << min ( 2 , 3 ) << std :: endl ; }Datei2.cpp :
#include <iostream>static void trace () { std :: cout << "Datei 2" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y ) { trace (); return a < b ? a : b ; }Mit der Einführung von Modulen in C++20 wurde das Schlüsselwort exportwieder in C++ eingeführt. Dadurch wurden Deklarationen wie diese wieder möglich:
import std ;using std :: is_base_of_v ;export class Atom { // ... };export template < typename T > concept ExtendsAtom = is_base_of_v < Atom , T > ;export template < ExtendsAtom Instance , typename ... Bases > class Cluster : public Bases ... { private : Instance x ; public : explicit Cluster ( Instance x , Bases && ... bases ) : Bases ( bases )..., x { x } {}// ... };Die Vorteile der Kompilierungsgeschwindigkeit, die durch exportierte Templates erzielt werden sollten, werden ohnehin von Modulen geboten, wodurch das Feature im Wesentlichen überflüssig und durch Module ersetzt wird.
Generische Programmierfunktionen in anderen Sprachen
Ursprünglich war das Konzept der Templates in einigen Sprachen, wie Java und C# 1.0, nicht enthalten. Javas Einführung von Generics ahmt das Verhalten von Templates nach, ist aber technisch anders. C# führte Generics (parametrisierte Typen) in .NET 2.0 ein. Die Generics in Ada existierten bereits vor den C++-Templates.
Obwohl C++-Templates, Java-Generics und .NET- Generics oft als ähnlich betrachtet werden, ahmen Generics lediglich das grundlegende Verhalten von C++- Templates nach. [ 11 ] Einige der fortgeschrittenen Template-Funktionen, die von Bibliotheken wie Boost und STLSoft sowie von STL-Implementierungen für Template-Metaprogrammierung genutzt werden (explizite oder partielle Spezialisierung, Standard-Template-Argumente, Template-Nicht-Typ-Argumente, Template-Template-Argumente usw.), sind mit Generics nicht verfügbar.
In C++-Templates wurden Kompilierzeitfälle traditionell durch Pattern Matching der Template-Argumente behandelt. Beispielsweise wird die Template-Basisklasse im unten stehenden Fakultätsbeispiel durch Vergleich mit 0 implementiert, anstatt einen Ungleichheitstest durchzuführen, der zuvor nicht möglich war. Mit der Einführung von Standardbibliotheksfunktionen in C++11 std::conditionalwurde jedoch eine weitere, flexiblere Möglichkeit zur bedingten Template-Instanziierung geschaffen.
// Induktionsvorlage < unsigned int N > struct Factorial { static constexpr unsigned int value = N * Factorial < N - 1 > :: value ; };// Basisfall durch Template-Spezialisierung: template <> struct Factorial < 0 > { static constexpr unsigned int value = 1 ; };Mit diesen Definitionen kann man beispielsweise 6! zur Kompilierzeit mit dem Ausdruck berechnen Factorial<6>::value.
Alternativ kann man constexprin C++11 / if constexprin C++17 solche Werte direkt mithilfe einer Funktion zur Kompilierzeit berechnen:
template < unsigned int N > [[ nodiscard ]] constexpr unsigned int factorial () noexcept { if constexpr ( N <= 1 ) { return 1 ; } else { return N * factorial < N - 1 > (); } }Aus diesem Grund wird Template-Metaprogrammierung heute hauptsächlich für Operationen an Typen verwendet.
Siehe auch
- Template-Metaprogrammierung
- Metaprogrammierung
- Monomorphisierung
- Generische Programmierung
- Nur Kopfzeile
- Ein fehlgeschlagener Austausch ist kein Fehler.
- Merkwürdig wiederkehrendes Vorlagenmuster
- Liste der C++-Template-Bibliotheken
Referenzen
- ^ Stroustrup, Bjarne (8. September 2004). „Die Programmiersprache C++“ . Stroustrup.com (persönliche Homepage) (3. Sonderausgabe).
- ^ „C++ Templates are Turing Complete“ (PDF) . ubietylab.net . Archiviert vom Original (PDF) am 1. November 2013.
- ^ Lippman, Stan (11. August 2004). „Warum C++ sowohl Klassen als auch Typnamen für Typparameter unterstützt“ . Microsoft Developers Network (MSDN) .
- ^ "std::max" . cppreference.com . Abgerufen am 21. Juli 2025 .
- ^ „P1141R1 – Ein weiterer Ansatz für eingeschränkte Deklarationen“ . Archiviert vom Original am 11.11.2018 . Abgerufen am 11.11.2018 .
- ^ Vandevoorde, Daveed; Josuttis, Nicolai (2002). C++-Vorlagen: Der vollständige Leitfaden . Addison Wesley . ISBN 978-0-201-73484-3Die
- ^ "Klassenvorlage" . cppreference.com . 7. Oktober 2025.
- ^ Herb Sutter (3. März 2003). „Warum wir uns Exporte nicht leisten können“ (PDF) . open-std.org . WG 21.
- ^ „Exportübersicht“ . comeaucomputing.com . Comeau Computing. Archiviert vom Original am 2. Juni 2003.
- ^ „Übersicht der exportierten Vorlage“ . comeaucomputing.com . Comeau Computing. Archiviert vom Original am 22. April 2003.
- ^ „Unterschiede zwischen C++ Templates und C# Generics (C# Programming Guide)“ . 12. März 2024.
Externe Links
- Demonstration der Turing-Vollständigkeit von C++-Templates ( Implementierung des Lambda-Kalküls )