Suche…
Einführung
Klassen, Funktionen und (seit C ++ 14) Variablen können erstellt werden. Eine Vorlage ist ein Stück Code mit einigen freien Parametern, die zu einer konkreten Klasse, Funktion oder Variablen werden, wenn alle Parameter angegeben werden. Parameter können Typen, Werte oder selbst Vorlagen sein. Eine bekannte Vorlage ist std::vector
, die zu einem konkreten Containertyp wird, wenn der Elementtyp angegeben wird, z. B. std::vector<int>
.
Syntax
- Deklaration der Vorlage < template-parameter-list >
- exportiere die Deklaration < template-parameter-list > / * bis C ++ 11 * /
- Deklaration der Vorlage <>
- Template - Deklaration
- extern Template - Deklaration / * da C ++ 11 * /
- Template < Template-Parameter-List > Klasse ... ( Opt ) Bezeichner ( Opt )
- template <template-Parameter-Liste> Klassenkennung (opt) = id-expression
- template < template-parameter-list > typename ... ( opt ) Bezeichner ( opt ) / * seit C ++ 17 * /
- template <template-Parameter-Liste> Typname Kennung (opt) = id-Ausdruck / * da C ++ 17 * /
- Postfix-Ausdruck . Template- ID-Ausdruck
- Postfix-Ausdruck -> Vorlagen- ID-Ausdruck
- Nested-Name-Specifier-
template
simple-template-id::
Bemerkungen
Die template
ist ein Schlüsselwort mit fünf verschiedenen Bedeutungen in der C ++ - Sprache, je nach Kontext.
Wenn eine Liste mit in
<>
eingeschlossenen Vorlagenparametern folgt, deklariert sie eine Vorlage, beispielsweise eine Klassenvorlage , eine Funktionsvorlage oder eine teilweise Spezialisierung einer vorhandenen Vorlage.template <class T> void increment(T& x) { ++x; }
Wenn von einem leeren
<>
gefolgt wird, wird eine explizite (vollständige) Spezialisierung angegeben .template <class T> void print(T x); template <> // <-- keyword used in this sense here void print(const char* s) { // output the content of the string printf("%s\n", s); }
Wenn eine Deklaration ohne
<>
folgt, bildet sie eine explizite Instantiierungsdeklaration oder -definition.template <class T> std::set<T> make_singleton(T x) { return std::set<T>(x); } template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
Innerhalb einer Vorlage Parameterliste, führt sie einen Template - Template - Parameter .
template <class T, template <class U> class Alloc> // ^^^^^^^^ keyword used in this sense here class List { struct Node { T value; Node* next; }; Alloc<Node> allocator; Node* allocate_node() { return allocator.allocate(sizeof(T)); } // ... };
Nach dem Bereichsauflösungsoperator
::
und den Klassenmitgliedszugriffsoperatoren.
und->
gibt an, dass der folgende Name eine Vorlage ist.struct Allocator { template <class T> T* allocate(); }; template <class T, class Alloc> class List { struct Node { T value; Node* next; } Alloc allocator; Node* allocate_node() { // return allocator.allocate<Node>(); // error: < and > are interpreted as // comparison operators return allocator.template allocate<Node>(); // ok; allocate is a template // ^^^^^^^^ keyword used in this sense here } };
Vor C ++ 11 konnte eine Vorlage mit dem export
deklariert werden , um daraus eine exportierte Vorlage zu machen. Die Definition einer exportierten Vorlage muss nicht in jeder Übersetzungseinheit vorhanden sein, in der die Vorlage instanziiert wird. Zum Beispiel sollte folgendes funktionieren:
foo.h
:
#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif
foo.cpp
:
#include "foo.h"
template <class T> T identity(T x) { return x; }
main.cpp
:
#include "foo.h"
int main() {
const int x = identity(42); // x is 42
}
Aufgrund von Schwierigkeiten bei der Implementierung wurde das export
von den meisten großen Compilern nicht unterstützt. Es wurde in C ++ 11 entfernt. Das export
Schlüsselwort darf jetzt überhaupt nicht verwendet werden. Stattdessen müssen in der Regel Vorlagen in Kopfzeilen definiert werden (im Gegensatz zu Funktionen, die keine Vorlagen sind und normalerweise nicht in Kopfzeilen definiert sind). Siehe Warum können Vorlagen nur in der Headerdatei implementiert werden?
Funktionsvorlagen
Das Templating kann auch auf Funktionen (wie auch auf traditionelle Strukturen) mit dem gleichen Effekt angewendet werden.
// 'T' stands for the unknown type
// Both of our arguments will be of the same type.
template<typename T>
void printSum(T add1, T add2)
{
std::cout << (add1 + add2) << std::endl;
}
Diese kann dann wie Strukturvorlagen verwendet werden.
printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);
In beiden Fällen wird das Template-Argument verwendet, um die Typen der Parameter zu ersetzen. Das Ergebnis funktioniert wie eine normale C ++ - Funktion (wenn die Parameter nicht mit dem Vorlagentyp übereinstimmen, wendet der Compiler die Standardkonvertierungen an).
Eine weitere Eigenschaft von Template-Funktionen (im Gegensatz zu Template-Klassen) besteht darin, dass der Compiler die Template-Parameter anhand der an die Funktion übergebenen Parameter ableiten kann.
printSum(4, 5); // Both parameters are int.
// This allows the compiler deduce that the type
// T is also int.
printSum(5.0, 4); // In this case the parameters are two different types.
// The compiler is unable to deduce the type of T
// because there are contradictions. As a result
// this is a compile time error.
Mit dieser Funktion können wir den Code vereinfachen, wenn wir Vorlagenstrukturen und Funktionen kombinieren. In der Standardbibliothek gibt es ein gemeinsames Muster, mit dem wir die template structure X
mit der make_X()
.
// The make_X pattern looks like this.
// 1) A template structure with 1 or more template types.
template<typename T1, typename T2>
struct MyPair
{
T1 first;
T2 second;
};
// 2) A make function that has a parameter type for
// each template parameter in the template structure.
template<typename T1, typename T2>
MyPair<T1, T2> make_MyPair(T1 t1, T2 t2)
{
return MyPair<T1, T2>{t1, t2};
}
Wie hilft das?
auto val1 = MyPair<int, float>{5, 8.7}; // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7); // Create object using the types of the paramters.
// In this code both val1 and val2 are the same
// type.
Hinweis: Dies ist nicht dazu gedacht, den Code zu verkürzen. Dies soll den Code robuster machen. Dadurch können die Typen geändert werden, indem der Code an einer einzelnen Stelle und nicht an mehreren Stellen geändert wird.
Weiterleitung von Argumenten
Vorlage akzeptiert möglicherweise lvalue- und rvalue-Referenzen unter Verwendung der Weiterleitungsreferenz :
template <typename T>
void f(T &&t);
In diesem Fall wird der tatsächliche Typ von t
abhängig vom Kontext hergeleitet:
struct X { };
X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)
Im ersten Fall wird der Typ T
als Verweis auf X
( X&
) hergeleitet, und der Typ von t
ist der Wertewert auf X
, während im zweiten Fall der Typ von T
als X
und der Typ von t
als Wertewert abgeleitet wird zu X
( X&&
).
Hinweis: Beachten Sie, dass decltype(t)
im ersten Fall dasselbe wie T
ist, im zweiten Fall jedoch nicht.
Um t
perfekt an eine andere Funktion weiterzuleiten, unabhängig davon, ob es sich um eine lvalue- oder eine rvalue-Referenz handelt, muss std::forward
:
template <typename T>
void f(T &&t) {
g(std::forward<T>(t));
}
Weiterleitungsreferenzen können mit variadischen Vorlagen verwendet werden:
template <typename... Args>
void f(Args&&... args) {
g(std::forward<Args>(args)...);
}
Hinweis: Weiterleitungsreferenzen können nur für Vorlagenparameter verwendet werden, z. B. ist im folgenden Code v
eine rvalue-Referenz und keine Weiterleitungsreferenz:
#include <vector>
template <typename T>
void f(std::vector<T> &&v);
Grundlegende Klassenvorlage
Die Grundidee einer Klassenvorlage ist, dass der Vorlagenparameter zur Kompilierzeit durch einen Typ ersetzt wird. Das Ergebnis ist, dass dieselbe Klasse für mehrere Typen wiederverwendet werden kann. Der Benutzer gibt an, welcher Typ verwendet wird, wenn eine Variable der Klasse deklariert wird. Drei Beispiele dafür sind in main()
:
#include <iostream>
using std::cout;
template <typename T> // A simple class to hold one number of any type
class Number {
public:
void setNum(T n); // Sets the class field to the given number
T plus1() const; // returns class field's "follower"
private:
T num; // Class field
};
template <typename T> // Set the class field to the given number
void Number<T>::setNum(T n) {
num = n;
}
template <typename T> // returns class field's "follower"
T Number<T>::plus1() const {
return num + 1;
}
int main() {
Number<int> anInt; // Test with an integer (int replaces T in the class)
anInt.setNum(1);
cout << "My integer + 1 is " << anInt.plus1() << "\n"; // Prints 2
Number<double> aDouble; // Test with a double
aDouble.setNum(3.1415926535897);
cout << "My double + 1 is " << aDouble.plus1() << "\n"; // Prints 4.14159
Number<float> aFloat; // Test with a float
aFloat.setNum(1.4);
cout << "My float + 1 is " << aFloat.plus1() << "\n"; // Prints 2.4
return 0; // Successful completion
}
Template-Spezialisierung
Sie können die Implementierung für bestimmte Instanziierungen einer Vorlagenklasse / -methode definieren.
Zum Beispiel, wenn Sie haben:
template <typename T>
T sqrt(T t) { /* Some generic implementation */ }
Sie können dann schreiben:
template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }
Ein Benutzer, der sqrt(4.0)
schreibt, erhält dann die generische Implementierung, während sqrt(4)
die spezialisierte Implementierung erhält.
Teilweise Schablonenspezialisierung
Im Gegensatz zu einer vollständigen Schablonenspezialisierung ermöglicht die partielle Schablonenspezialisierung die Einführung einer Schablone mit einigen der Argumente der vorhandenen Schablone. Die teilweise Vorlagenspezialisierung ist nur für Vorlagenklassen / -strukturen verfügbar:
// Common case:
template<typename T, typename U>
struct S {
T t_val;
U u_val;
};
// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
double another_value;
int foo(double arg) {// Do something}
};
Wie oben gezeigt, können partielle Vorlagenspezialisierungen völlig unterschiedliche Datensätze und Funktionsmitglieder einführen.
Wenn eine teilweise spezialisierte Vorlage instanziiert wird, wird die am besten geeignete Spezialisierung ausgewählt. Zum Beispiel definieren wir eine Vorlage und zwei Teilspezialisierungen:
template<typename T, typename U, typename V>
struct S {
static void foo() {
std::cout << "General case\n";
}
};
template<typename U, typename V>
struct S<int, U, V> {
static void foo() {
std::cout << "T = int\n";
}
};
template<typename V>
struct S<int, double, V> {
static void foo() {
std::cout << "T = int, U = double\n";
}
};
Nun die folgenden Anrufe:
S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();
wird drucken
General case
T = int
T = int, U = double
Funktionsvorlagen dürfen nur vollständig spezialisiert sein:
template<typename T, typename U>
void foo(T t, U u) {
std::cout << "General case: " << t << " " << u << std::endl;
}
// OK.
template<>
void foo<int, int>(int a1, int a2) {
std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}
void invoke_foo() {
foo(1, 2.1); // Prints "General case: 1 2.1"
foo(1,2); // Prints "Two ints: 1 2"
}
// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
std::cout << "General case: " << t << " " << u << std::endl;
}
Standard-Vorlagenparameterwert
Wie bei den Funktionsargumenten können Vorlagenparameter ihre Standardwerte haben. Alle Vorlagenparameter mit einem Standardwert müssen am Ende der Vorlagenparameterliste deklariert werden. Die Grundidee ist, dass die Vorlagenparameter mit dem Standardwert während der Instantiierung der Vorlage weggelassen werden können.
Einfaches Beispiel für die Verwendung von Standardvorlagenparameterwerten:
template <class T, size_t N = 10>
struct my_array {
T arr[N];
};
int main() {
/* Default parameter is ignored, N = 5 */
my_array<int, 5> a;
/* Print the length of a.arr: 5 */
std::cout << sizeof(a.arr) / sizeof(int) << std::endl;
/* Last parameter is omitted, N = 10 */
my_array<int> b;
/* Print the length of a.arr: 10 */
std::cout << sizeof(b.arr) / sizeof(int) << std::endl;
}
Alias-Vorlage
Grundbeispiel:
template<typename T> using pointer = T*;
Diese Definition macht den pointer<T>
einem Alias von T*
. Zum Beispiel:
pointer<int> p = new int; // equivalent to: int* p = new int;
Aliasvorlagen können nicht spezialisiert werden. Diese Funktionalität kann jedoch indirekt erhalten werden, indem sie sich auf einen verschachtelten Typ in einer Struktur beziehen:
template<typename T>
struct nonconst_pointer_helper { typedef T* type; };
template<typename T>
struct nonconst_pointer_helper<T const> { typedef T* type; };
template<typename T> using nonconst_pointer = nonconst_pointer_helper<T>::type;
Parameter für Vorlagenvorlagen
Manchmal möchten wir einen Schablonentyp in die Vorlage einreichen, ohne dessen Werte festzulegen. Dafür werden Vorlagenvorlagenparameter erstellt. Sehr einfache Vorlagenparameterbeispiele:
template <class T>
struct Tag1 { };
template <class T>
struct Tag2 { };
template <template <class> class Tag>
struct IntTag {
typedef Tag<int> type;
};
int main() {
IntTag<Tag1>::type t;
}
#include <vector>
#include <iostream>
template <class T, template <class...> class C, class U>
C<T> cast_all(const C<U> &c) {
C<T> result(c.begin(), c.end());
return result;
}
int main() {
std::vector<float> vf = {1.2, 2.6, 3.7};
auto vi = cast_all<int>(vf);
for(auto &&i: vi) {
std::cout << i << std::endl;
}
}
Nicht-Typ-Vorlagenargumente mit auto deklarieren
Vor C ++ 17 mussten Sie beim Schreiben eines nicht typisierten Parameters der Vorlage zuerst seinen Typ angeben. So wurde ein allgemeines Muster zum Schreiben:
template <class T, T N>
struct integral_constant {
using type = T;
static constexpr T value = N;
};
using five = integral_constant<int, 5>;
Für komplizierte Ausdrücke bedeutet dies jedoch, dass Sie decltype(expr), expr
schreiben decltype(expr), expr
wenn Sie Templates instantiieren. Die Lösung besteht darin, diese Sprache zu vereinfachen und einfach auto
zuzulassen:
template <auto N>
struct integral_constant {
using type = decltype(N);
static constexpr type value = N;
};
using five = integral_constant<5>;
Leeres benutzerdefiniertes Deleter für unique_ptr
Ein schönes motivierendes Beispiel kann der Versuch sein, die leere Basisoptimierung mit einem benutzerdefinierten Deleter für unique_ptr
zu kombinieren. Verschiedene C-API-Deleter haben unterschiedliche Rückgabetypen, aber das ist uns egal - wir möchten nur, dass für jede Funktion etwas funktioniert:
template <auto DeleteFn>
struct FunctionDeleter {
template <class T>
void operator()(T* ptr) const {
DeleteFn(ptr);
}
};
template <T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;
Und jetzt können Sie einfach einen beliebigen Funktionszeiger verwenden, der ein Argument des Typs T
unabhängig vom Rückgabetyp als nicht-typisierten Parameter für die Vorlage übernehmen kann und daraus einen unique_ptr
die Größe unique_ptr
erhält:
unique_ptr_deleter<std::FILE, std::fclose> p;
Nicht-Typ-Vorlagenparameter
Abgesehen von Types als Template-Parameter dürfen wir Werte von konstanten Ausdrücken deklarieren, die eines der folgenden Kriterien erfüllen:
- Integral- oder Aufzählungstyp,
- Zeiger auf Objekt oder Zeiger auf Funktion
- lWertreferenz auf Objekt oder lWertreferenz auf Funktion,
- Zeiger auf Mitglied,
-
std::nullptr_t
.
Wie alle Vorlagenparameter können Vorlagenparameter, die nicht vom Typ sind, explizit angegeben, vorgegeben oder implizit über die Vorlagen-Argumentableitung abgeleitet werden.
Beispiel für die Verwendung von nicht typisierten Vorlagenparametern:
#include <iostream>
template<typename T, std::size_t size>
std::size_t size_of(T (&anArray)[size]) // Pass array by reference. Requires.
{ // an exact size. We allow all sizes
return size; // by using a template "size".
}
int main()
{
char anArrayOfChar[15];
std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "\n";
int anArrayOfData[] = {1,2,3,4,5,6,7,8,9};
std::cout << "anArrayOfData: " << size_of(anArrayOfData) << "\n";
}
Beispiel für die explizite Angabe von Vorlagenparametern vom Typ und nicht vom Typ:
#include <array>
int main ()
{
std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}
Nicht-Typ-Vorlagenparameter sind eine der Möglichkeiten, um die Wiederholung von Vorlagen zu erreichen, und ermöglichen die Durchführung von Metaprogrammierungen .
Variadische Vorlagendatenstrukturen
Es ist häufig nützlich, Klassen oder Strukturen zu definieren, die eine variable Anzahl und einen Typ von Datenelementen haben, die zur Kompilierzeit definiert werden. Das kanonische Beispiel ist std::tuple
. In manchen std::tuple
müssen Sie jedoch Ihre eigenen benutzerdefinierten Strukturen definieren. Hier ist ein Beispiel, in dem die Struktur mithilfe von Compounding definiert wird (anstelle von Vererbung wie bei std::tuple
. Beginnen Sie mit der allgemeinen (leeren) Definition, die in der späteren Spezialisierung auch als Basisfall für die Beendigung der Rekrutierung dient:
template<typename ... T>
struct DataStructure {};
Dies erlaubt uns bereits, eine leere Struktur, DataStructure<> data
, zu definieren, obwohl dies noch nicht sehr nützlich ist.
Als nächstes kommt die rekursive Fallspezialisierung:
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
};
Dies reicht nun aus, um beliebige Datenstrukturen wie DataStructure<int, float, std::string> data(1, 2.1, "hello")
zu erstellen.
So was ist los? Beachten Sie zunächst, dass es sich hierbei um eine Spezialisierung handelt, deren Anforderung es ist, dass mindestens ein variadischer Vorlagenparameter (nämlich T
oben) vorhanden ist, ohne sich um die spezifische Zusammensetzung der Packung Rest
kümmern. Das Wissen , dass T
existiert erlaubt die Definition seines Datenelements first
. Der Rest der Daten wird rekursiv als DataStructure<Rest ... > rest
. Der Konstruktor initiiert beide Member, einschließlich eines rekursiven Konstruktoraufrufs für das rest
Member.
Um dies besser zu verstehen, können wir ein Beispiel DataStructure<int, float> data
: Angenommen, Sie haben eine Deklaration DataStructure<int, float> data
. Die Deklaration stimmt zuerst mit der Spezialisierung überein und ergibt eine Struktur mit den DataStructure<float> rest
int first
und DataStructure<float> rest
. Der rest
Definition paßt wieder diese Spezialisierung, seinen eigenen Schaffung float first
und DataStructure<> rest
Mitglieder. Diese letzte rest
entspricht schließlich der Definition des Basisfalls und erzeugt eine leere Struktur.
Sie können dies wie folgt visualisieren:
DataStructure<int, float>
-> int first
-> DataStructure<float> rest
-> float first
-> DataStructure<> rest
-> (empty)
Nun haben wir die Datenstruktur, die jedoch noch nicht besonders nützlich ist, da wir nicht einfach auf die einzelnen Datenelemente zugreifen können (um beispielsweise auf das letzte Mitglied der DataStructure<int, float, std::string> data
wir data.rest.rest.first
, was nicht gerade benutzerfreundlich ist). So fügen wir eine get
- Methode , um es (nur in der Spezialisierung erforderlich , da die Basis-Gehäusestruktur keine Daten zu get
):
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
...
template<size_t idx>
auto get()
{
return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
}
...
};
Wie Sie sehen, wird diese get
Member-Funktion selbst erstellt - diesmal auf dem Index des Member, das benötigt wird (die Verwendung kann z. B. data.get<1>()
, ähnlich wie bei std::tuple
). Die eigentliche Arbeit wird von einer statischen Funktion in der GetHelper
. Der Grund, warum wir die erforderliche Funktionalität nicht direkt in DataStructure
Definition definieren können get
liegt darin, dass wir (wie wir bald sehen werden) uns auf idx
spezialisieren idx
Es ist jedoch nicht möglich, eine Template-Member-Funktion zu spezialisieren, ohne die enthaltende Klasse zu spezialisieren Vorlage. Beachten Sie auch, dass die Verwendung eines auto
C ++ 14-Stil hier unser Leben erheblich vereinfacht, da wir sonst einen komplizierten Ausdruck für den Rückgabetyp benötigen.
Also weiter zur Helferklasse. Diesmal benötigen wir eine leere Vorwärtsdeklaration und zwei Spezialisierungen. Zuerst die Erklärung:
template<size_t idx, typename T>
struct GetHelper;
Nun der Basisfall (wenn idx==0
). In diesem Fall schicken wir einfach das first
Mitglied zurück:
template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
static T get(DataStructure<T, Rest...>& data)
{
return data.first;
}
};
In dem rekursiven Fall verringern wir idx
und das Aufrufen GetHelper
für den rest
Mitglied:
template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
static auto get(DataStructure<T, Rest...>& data)
{
return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
}
};
Angenommen, wir haben DataStructure<int, float> data
data.get<1>()
und benötigen data.get<1>()
. Das ruft GetHelper<1, DataStructure<int, float>>::get(data)
(die zweite Spezialisierung) auf, das wiederum GetHelper<0, DataStructure<float>>::get(data.rest)
, das schließlich zurückkehrt (von der 1. Spezialisierung, da jetzt idx
0 ist) data.rest.first
.
So, das war es! Hier ist der ganze Funktion Code, mit einigem Beispiel für die Verwendung in der main
#include <iostream>
template<size_t idx, typename T>
struct GetHelper;
template<typename ... T>
struct DataStructure
{
};
template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
DataStructure(const T& first, const Rest& ... rest)
: first(first)
, rest(rest...)
{}
T first;
DataStructure<Rest ... > rest;
template<size_t idx>
auto get()
{
return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
}
};
template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
static T get(DataStructure<T, Rest...>& data)
{
return data.first;
}
};
template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
static auto get(DataStructure<T, Rest...>& data)
{
return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
}
};
int main()
{
DataStructure<int, float, std::string> data(1, 2.1, "Hello");
std::cout << data.get<0>() << std::endl;
std::cout << data.get<1>() << std::endl;
std::cout << data.get<2>() << std::endl;
return 0;
}
Explizite Instantiierung
Eine explizite Instantiierungsdefinition erstellt und deklariert eine konkrete Klasse, Funktion oder Variable aus einer Vorlage, ohne sie noch zu verwenden. Auf eine explizite Instanziierung kann von anderen Übersetzungseinheiten aus verwiesen werden. Dies kann verwendet werden, um die Definition einer Vorlage in einer Header-Datei zu vermeiden, wenn diese nur mit einer begrenzten Anzahl von Argumenten instanziiert wird. Zum Beispiel:
// print_string.h
template <class T>
void print_string(const T* str);
// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);
Da print_string<char>
und print_string<wchar_t>
in print_string.cpp
explizit instanziiert print_string.cpp
, kann der Linker sie auch finden, obwohl die Vorlage print_string
nicht im Header definiert ist. Wenn diese expliziten Instanziierungsdeklarationen nicht vorhanden wären, würde wahrscheinlich ein Linker-Fehler auftreten. Siehe Warum können Vorlagen nur in der Headerdatei implementiert werden?
Wenn eine explizite Instanziierung Definition durch das vorangestellt ist extern
Schlüsselwort , wird es eine explizite Instanziierung Deklaration statt. Das Vorhandensein einer expliziten Instantiierungsdeklaration für eine bestimmte Spezialisierung verhindert die implizite Instantiierung der jeweiligen Spezialisierung innerhalb der aktuellen Übersetzungseinheit. Ein Verweis auf diese Spezialisierung, der andernfalls eine implizite Instantiierung verursachen würde, kann stattdessen auf eine explizite Instantiierungsdefinition in derselben oder einer anderen TU verweisen.
foo.h
#ifndef FOO_H
#define FOO_H
template <class T> void foo(T x) {
// complicated implementation
}
#endif
foo.cpp
#include "foo.h"
// explicit instantiation definitions for common cases
template void foo(int);
template void foo(double);
main.cpp
#include "foo.h"
// we already know foo.cpp has explicit instantiation definitions for these
extern template void foo(double);
int main() {
foo(42); // instantiates foo<int> here;
// wasteful since foo.cpp provides an explicit instantiation already!
foo(3.14); // does not instantiate foo<double> here;
// uses instantiation of foo<double> in foo.cpp instead
}