Function template
Eine Funktionsvorlage definiert eine Familie von Funktionen.
Syntax
template
<
parameter-list
>
function-declaration
|
(1) | ||||||||
template
<
parameter-list
>
requires
constraint
function-declaration
|
(2) | (seit C++20) | |||||||
| function-declaration-with-placeholders | (3) | (seit C++20) | |||||||
export
template
<
parameter-list
>
function-declaration
|
(4) | (entfernt in C++11) | |||||||
Erklärung
| parameter-list | - | eine nicht-leere, kommagetrennte Liste der template parameters , von denen jeder entweder ein constant parameter , ein type parameter , ein template parameter oder ein parameter pack von einem dieser Typen ist (since C++11) . Wie bei jeder Template können Parameter constrained sein (since C++20) |
| function-declaration | - | eine function declaration . Der deklarierte Funktionsname wird zu einem Template-Namen. |
| constraint | - | ein constraint expression , der die von dieser Funktions-Template akzeptierten Template-Parameter einschränkt |
|
function-declaration-
with-placeholders |
- | eine function declaration , bei der der Typ mindestens eines Parameters den Platzhalter auto oder Concept auto verwendet: Die Template-Parameterliste enthält einen erfundenen Parameter für jeden Platzhalter (siehe Abgekürzte Funktions-Templates unten) |
|
|
(until C++11) |
Abgekürzte Funktions-TemplatesWenn Platzhaltertypen (entweder auto oder Concept auto ) in der Parameterliste einer Funktionsdeklaration oder einer Funktions-Template-Deklaration erscheinen, deklariert die Deklaration ein Funktions-Template, und ein erfundener Template-Parameter für jeden Platzhalter wird an die Template-Parameterliste angehängt: void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&); template<class T, C U> void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z); Abgekürzte Funktions-Templates können wie alle Funktions-Templates spezialisiert werden. template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
|
(seit C++20) |
Funktionsvorlagen-Signatur
Jede Funktionsvorlage hat eine Signatur.
Die Signatur eines template-head ist die template parameter list , ausgenommen Template-Parameternamen und default arguments , und requires-clause (falls vorhanden) (since C++20) .
Die Signatur eines Funktions-Templates enthält den Namen, die Parameter-Typen-Liste, den Rückgabetyp , nachgestellte requires-Klausel (falls vorhanden) (seit C++20) , und die Signatur des template-head . Mit Ausnahme der folgenden Fälle enthält ihre Signatur auch den umschließenden Namensraum.
Wenn die Funktionsvorlage ein Klassenmitglied ist, enthält ihre Signatur die Klasse, zu der die Funktion gehört, anstelle des umschließenden Namensraums. Ihre Signatur enthält auch die nachgestellte Requires-Klausel (falls vorhanden) (seit C++20) , Ref-Qualifier (falls vorhanden) und (seit C++11) CV -Qualifizierer (falls vorhanden).
|
Wenn die Funktionsvorlage ein friend mit einer Einschränkung ist, die umschließende Template-Parameter enthält, enthält ihre Signatur die umschließende Klasse anstelle des umschließenden Namensraums. |
(since C++20) |
Instanziierung von Funktionsvorlagen
Eine Funktionsvorlage für sich allein ist kein Typ und keine Funktion. Aus einer Quelldatei, die nur Vorlagendefinitionen enthält, wird kein Code generiert. Damit Code erzeugt werden kann, muss eine Vorlage instanziiert werden: Die Vorlagenargumente müssen bestimmt werden, damit der Compiler eine tatsächliche Funktion (oder Klasse, aus einer Klassenvorlage) generieren kann.
Explizite Instanziierung
template
Rückgabetyp
Name
<
Argumentenliste
>
(
Parameterliste
)
;
|
(1) | ||||||||
template
Rückgabetyp
Name
(
Parameterliste
)
;
|
(2) | ||||||||
extern
template
Rückgabetyp
Name
<
Argumentenliste
>
(
Parameterliste
)
;
|
(3) | (seit C++11) | |||||||
extern
template
Rückgabetyp
Name
(
Parameterliste
)
;
|
(4) | (seit C++11) | |||||||
Eine explizite Instanziierungsdefinition erzwingt die Instanziierung der Funktion oder Memberfunktion, auf die sie verweist. Sie kann im Programm überall nach der Template-Definition erscheinen und darf für eine gegebene Argumentliste nur einmal im Programm vorkommen, ohne dass eine Diagnose erforderlich ist.
|
Eine explizite Instanziierungsdeklaration (ein extern Template) verhindert implizite Instanziierungen: Der Code, der ansonsten eine implizite Instanziierung verursachen würde, muss die explizite Instanziierungsdefinition verwenden, die an anderer Stelle im Programm bereitgestellt wird. |
(since C++11) |
Ein nachfolgendes Template-Argument kann in einer expliziten Instanziierung einer Funktions-Template-Spezialisierung oder einer Spezialisierung einer Member-Funktions-Template weggelassen werden, wenn es vom Funktionsparameter abgeleitet werden kann:
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // instanziiert f<double>(double) template void f<>(char); // instanziiert f<char>(char), Template-Argument abgeleitet template void f(int); // instanziiert f<int>(int), Template-Argument abgeleitet
Explizite Instanziierung eines Funktions-Templates oder einer Member-Funktion eines Klassen-Templates kann nicht
inline
oder
constexpr
verwenden. Wenn die Deklaration der expliziten Instanziierung einen implizit deklarierten speziellen Member benennt, ist das Programm fehlerhaft.
Explizite Instanziierung eines Konstruktors kann keine Template-Parameterliste verwenden (Syntax (1) ), was auch niemals notwendig ist, da sie abgeleitet werden können (Syntax (2) ).
|
Explizite Instanziierung eines potenziellen Destruktors muss den ausgewählten Destruktor der Klasse benennen. |
(since C++20) |
Explizite Instanziierungsdeklarationen unterdrücken nicht die implizite Instanziierung von inline -Funktionen, auto -Deklarationen, Referenzen und Klassentemplatespezialisierungen. (daher wird die inline-Funktion, die Gegenstand einer expliziten Instanziierungsdeklaration ist, bei ODR-Nutzung implizit für Inlining instanziiert, aber ihre out-of-line-Kopie wird in dieser Übersetzungseinheit nicht erzeugt)
Explizite Instanziierungsdefinition einer Funktionsvorlage mit Standardargumenten ist keine Verwendung der Argumente und versucht nicht, sie zu initialisieren:
char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // OK auch wenn &p kein int ist.
Implizite Instanziierung
Wenn Code auf eine Funktion in einem Kontext verweist, der das Vorhandensein der Funktionsdefinition erfordert , oder wenn die Existenz der Definition die Semantik des Programms beeinflusst (seit C++11) , und diese spezielle Funktion nicht explizit instanziiert wurde, erfolgt eine implizite Instanziierung. Die Liste der Template-Argumente muss nicht angegeben werden, wenn sie aus dem Kontext abgeleitet werden kann .
#include <iostream> template<typename T> void f(T s) { std::cout << s << '\n'; } int main() { f<double>(1); // instanziiert und ruft f<double>(double) auf f<>('a'); // instanziiert und ruft f<char>(char) auf f(7); // instanziiert und ruft f<int>(int) auf void (*pf)(std::string) = f; // instanziiert f<string>(string) pf("∇"); // ruft f<string>(string) auf }
|
Die Existenz einer Funktionsdefinition wird als semantikbeeinflussend für das Programm betrachtet, wenn die Funktion für konstante Auswertung benötigt wird durch einen Ausdruck, selbst wenn die konstante Auswertung des Ausdrucks nicht erforderlich ist oder wenn die konstante Ausdrucksauswertung die Definition nicht verwendet. template<typename T> constexpr int f() { return T::value; } template<bool B, typename T> void g(decltype(B ? f<T>() : 0)); template<bool B, typename T> void g(...); template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})); template<bool B, typename T> void h(...); void x() { g<false, int>(0); // OK: B ? f<T>() : 0 wird nicht potentiell konstant ausgewertet h<false, int>(0); // Fehler: instanziiert f<int> obwohl B zu false ausgewertet wird // und die Listeninitialisierung von int aus int keine Verengung sein darf } |
(seit C++11) |
Hinweis: Das vollständige Weglassen von
<>
ermöglicht es der
Überlagerungsauflösung
, sowohl Template- als auch Nicht-Template-Überladungen zu prüfen.
Template-Argument-Deduktion
Um eine Funktionsvorlage zu instanziieren, muss jedes Vorlagenargument bekannt sein, aber nicht jedes Vorlagenargument muss angegeben werden. Wenn möglich, leitet der Compiler die fehlenden Vorlagenargumente aus den Funktionsargumenten ab. Dies geschieht, wenn ein Funktionsaufruf versucht wird und wenn die Adresse einer Funktionsvorlage genommen wird.
template<typename To, typename From> To convert(From f); void g(double d) { int i = convert<int>(d); // ruft convert<int,double>(double) auf char c = convert<char>(d); // ruft convert<char,double>(double) auf int(*ptr)(float) = convert; // instanziiert convert<int, float>(float) }
Dieser Mechanismus ermöglicht die Verwendung von Template-Operatoren, da es keine Syntax gibt, um Template-Argumente für einen Operator anzugeben, außer durch Umschreiben als Funktionsaufrufausdruck.
Template-Argument-Deduktion findet statt nach der Namenssuche für Funktions-Templates (welche argumentabhängige Suche beinhalten kann) und vor der Überladungsauflösung .
Siehe template argument deduction für Details.
Explizite Template-Argumente
Template-Argumente einer Funktionsvorlage können bezogen werden aus
- Template-Argument-Deduktion
- Standard-Template-Argumente
- explizit angegeben, was in den folgenden Kontexten erfolgen kann:
-
- in einem Funktionsaufrufausdruck
- wenn die Adresse einer Funktion genommen wird
- wenn eine Referenz auf eine Funktion initialisiert wird
- wenn ein Zeiger auf eine Elementfunktion gebildet wird
- in einer expliziten Spezialisierung
- in einer expliziten Instanziierung
- in einer Friend-Deklaration
Es gibt keine Möglichkeit, Template-Argumente explizit für überladene Operatoren , Konvertierungsfunktionen und Konstruktoren anzugeben, da sie ohne Verwendung des Funktionsnamens aufgerufen werden.
Die angegebenen Template-Argumente müssen in ihrer Art mit den Template-Parametern übereinstimmen (d.h. Typ für Typ, Konstante für Konstante und Template für Template). Es können nicht mehr Argumente als Parameter vorhanden sein (es sei denn, ein Parameter ist ein Parameter-Pack, in welchem Fall für jeden Nicht-Pack-Parameter ein Argument vorhanden sein muss) (seit C++11) .
Die angegebenen konstanten Argumente müssen entweder mit den Typen der entsprechenden konstanten Template-Parameter übereinstimmen oder in diese konvertierbar sein .
Die Funktionsparameter, die nicht an der Template-Argumentableitung teilnehmen (z.B. wenn die entsprechenden Template-Argumente explizit angegeben sind), unterliegen impliziten Konvertierungen zum Typ des entsprechenden Funktionsparameters (wie bei der üblichen Überlagerungsauflösung ).
|
Ein explizit angegebener Template-Parameter-Pack kann durch Template-Argument-Deduktion erweitert werden, wenn zusätzliche Argumente vorhanden sind: template<class... Types> void f(Types... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} } |
(seit C++11) |
Template-Argument-Substitution
Wenn alle Template-Argumente angegeben, abgeleitet oder von Standard-Template-Argumenten übernommen wurden, wird jede Verwendung eines Template-Parameters in der Funktionsparameterliste durch die entsprechenden Template-Argumente ersetzt.
Substitutionsfehler (das heißt, das Fehlschlagen der Ersetzung von Template-Parametern durch die abgeleiteten oder bereitgestellten Template-Argumente) einer Funktionsvorlage entfernt die Funktionsvorlage aus dem Overload-Set . Dies ermöglicht verschiedene Möglichkeiten, Overload-Sets mittels Template-Metaprogrammierung zu manipulieren: siehe SFINAE für Details.
Nach der Substitution werden alle Funktionsparameter vom Array- und Funktionstyp in Zeiger angepasst und alle obersten cv-Qualifizierer von Funktionsparametern entfernt (wie in einer regulären Funktionsdeklaration ).
Die Entfernung der obersten cv-Qualifizierer beeinflusst nicht den Typ des Parameters, wie er innerhalb der Funktion erscheint:
template<class T> void f(T t); template<class X> void g(const X x); template<class Z> void h(Z z, Z* zp); // zwei verschiedene Funktionen mit demselben Typ, aber // innerhalb der Funktion hat t unterschiedliche cv-Qualifikationen f<int>(1); // Funktionstyp ist void(int), t ist int f<const int>(1); // Funktionstyp ist void(int), t ist const int // zwei verschiedene Funktionen mit demselben Typ und demselben x // (Zeiger auf diese beiden Funktionen sind nicht gleich, // und funktionslokale statische Variablen hätten unterschiedliche Adressen) g<int>(1); // Funktionstyp ist void(int), x ist const int g<const int>(1); // Funktionstyp ist void(int), x ist const int // nur Top-Level-cv-Qualifikatoren werden entfernt: h<const int>(1, NULL); // Funktionstyp ist void(int, const int*) // z ist const int, zp ist const int*
Funktions-Template-Überladung
Funktionsschablonen und Nicht-Schablonen-Funktionen können überladen werden.
Eine Nicht-Template-Funktion ist immer verschieden von einer Template-Spezialisierung mit demselben Typ. Spezialisierungen verschiedener Funktions-Templates sind immer voneinander verschieden, selbst wenn sie denselben Typ haben. Zwei Funktions-Templates mit demselben Rückgabetyp und derselben Parameterliste sind verschieden und können durch ihre explizite Template-Argumentliste unterschieden werden.
Wenn ein Ausdruck, der Typ- oder Konstanten-Template-Parameter verwendet, in der Funktionsparameterliste oder im Rückgabetyp erscheint, bleibt dieser Ausdruck Teil der Funktions-Template-Signatur für den Zweck des Überladens:
template<int I, int J> A<I+J> f(A<I>, A<J>); // Überladung #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // identisch mit #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // Überladung #2
Zwei Ausdrücke, die Template-Parameter enthalten, werden als äquivalent bezeichnet, wenn zwei Funktionsdefinitionen, die diese Ausdrücke enthalten, gemäß der ODR identisch wären, das heißt, die beiden Ausdrücke enthalten dieselbe Abfolge von Tokens, deren Namen durch Namenssuche auf dieselben Entitäten aufgelöst werden, außer dass Template-Parameter unterschiedlich benannt sein können. Zwei Lambda-Ausdrücke sind niemals äquivalent. (seit C++20)
template<int I, int J> void f(A<I+J>); // Template-Überladung #1 template<int K, int L> void f(A<K+L>); // äquivalent zu #1
Bei der Bestimmung, ob zwei abhängige Ausdrücke äquivalent sind, werden nur die beteiligten abhängigen Namen berücksichtigt, nicht die Ergebnisse der Namenssuche. Wenn mehrere Deklarationen derselben Vorlage sich im Ergebnis der Namenssuche unterscheiden, wird die erste solche Deklaration verwendet:
template<class T> decltype(g(T())) h(); // decltype(g(T())) ist ein abhängiger Typ int g(int); template<class T> decltype(g(T())) h() { // Neudeklaration von h() verwendet frühere Suche return g(T()); // obwohl die Suche hier g(int) findet } int i = h<int>(); // Template-Argument-Substitution schlägt fehl; g(int) // war beim ersten Deklaration von h() nicht im Gültigkeitsbereich
Zwei Funktionsvorlagen werden als äquivalent betrachtet, wenn
- sie werden im selben Gültigkeitsbereich deklariert
- sie haben denselben Namen
- sie haben äquivalente Template-Parameterlisten, was bedeutet, dass die Listen dieselbe Länge haben und für jedes entsprechende Parameterpaar alle folgenden Punkte zutreffen:
-
- die beiden Parameter sind von derselben Art (beide Typen, beide Konstanten oder beide Templates)
|
(since C++11) |
-
- falls konstant, sind ihre Typen äquivalent,
- falls Template, sind ihre Template-Parameter äquivalent,
|
(seit C++20) |
- die Ausdrücke, die Template-Parameter in ihren Rückgabetypen und Parameterlisten enthalten, sind äquivalent
|
(since C++20) |
Zwei potentially-evaluated (since C++20) Ausdrücke, die Template-Parameter enthalten, werden als funktional äquivalent bezeichnet, wenn sie nicht äquivalent sind, aber für jeden gegebenen Satz von Template-Argumenten die Auswertung der beiden Ausdrücke zum selben Wert führt.
Zwei Funktionsschablonen werden als funktional äquivalent betrachtet, wenn sie äquivalent sind, außer dass ein oder mehrere Ausdrücke, die Template-Parameter in ihren Rückgabetypen und Parameterlisten betreffen, funktional äquivalent sind.
|
Zusätzlich sind zwei Funktionsvorlagen funktional äquivalent aber nicht äquivalent , wenn ihre Constraints unterschiedlich spezifiziert sind, aber sie dieselbe Menge von Template-Argumentlisten akzeptieren und erfüllen. |
(since C++20) |
Wenn ein Programm Deklarationen von Funktions-Templates enthält, die funktional äquivalent , aber nicht äquivalent sind, ist das Programm fehlerhaft; keine Diagnose ist erforderlich.
// äquivalent template<int I> void f(A<I>, A<I+10>); // Überladung #1 template<int I> void f(A<I>, A<I+10>); // Neudeklaration von Überladung #1 // nicht äquivalent template<int I> void f(A<I>, A<I+10>); // Überladung #1 template<int I> void f(A<I>, A<I+11>); // Überladung #2 // funktional äquivalent aber nicht äquivalent // Dieses Programm ist fehlerhaft, keine Diagnose erforderlich template<int I> void f(A<I>, A<I+10>); // Überladung #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // funktional äquivalent
Wenn die gleiche Funktions-Templatespezialisierung auf mehr als eine überladene Funktionstemplate passt (dies ergibt sich oft aus Template-Argumentableitung ), partielles Ordering überladener Funktionstemplates wird durchgeführt, um die beste Übereinstimmung auszuwählen.
Insbesondere findet eine partielle Ordnung in den folgenden Situationen statt:
template<class X> void f(X a); template<class X> void f(X* a); int* p; f(p);
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
|
Dieser Abschnitt ist unvollständig
Grund: Mini-Beispiel |
template<class X> void f(X a); // first template f template<class X> void f(X* a); // second template f template<> void f<>(int *a) {} // explicit specialization // template argument deduction comes up with two candidates: // f<int*>(int*) and f<int>(int*) // partial ordering selects f<int>(int*) as more specialized
Informell bedeutet "A ist spezialisierter als B" "A akzeptiert weniger Typen als B".
Formal wird, um zu bestimmen, welche von zwei Funktions-Templates spezialisierter ist, der partielle Ordnungsprozess zunächst eines der beiden Templates wie folgt transformiert:
- Für jeden Typ, jede Konstante und jeden Template-Parameter, einschließlich Parameter-Packs, (seit C++11) wird ein eindeutiger fiktiver Typ, Wert oder Template generiert und in den Funktionstyp des Templates eingesetzt
-
Wenn nur eine der beiden verglichenen Funktionstemplates eine Member-Funktion ist und dieses Funktionstemplate ein nicht-statisches Member einer Klasse
Aist, wird ein neuer erster Parameter in seine Parameterliste eingefügt. Mit cv als CV-Qualifiern des Funktionstemplates und ref als Ref-Qualifier des Funktionstemplates (seit C++11) ist der neue Parametertyp cvA&außer wenn ref gleich&&ist, oder ref nicht vorhanden ist und der erste Parameter des anderen Templates einen Rvalue-Referenztyp hat, in diesem Fall ist der Typ cvA&&(seit C++11) . Dies unterstützt die Ordnung von Operatoren, die sowohl als Member- als auch als Nicht-Member-Funktionen gesucht werden:
struct A {}; template<class T> struct B { template<class R> int operator*(R&); // #1 }; template<class T, class R> int operator*(T&, R&); // #2 int main() { A a; B<A> b; b * a; // Template-Argument-Deduktion für int B<A>::operator*(R&) ergibt R=A // für int operator*(T&, R&), T=B<A>, R=A // Zum Zweck der partiellen Ordnung wird die Member-Template B<A>::operator* // transformiert in template<class R> int operator*(B<A>&, R&); // Partielle Ordnung zwischen // int operator*( T&, R&) T=B<A>, R=A // und int operator*(B<A>&, R&) R=A // wählt int operator*(B<A>&, A&) als spezialisierter aus }
Nachdem eine der beiden Vorlagen wie oben beschrieben transformiert wurde, template argument deduction wird ausgeführt, wobei die transformierte Vorlage als Argumentvorlage und der ursprüngliche Vorlagentyp der anderen Vorlage als Parameter-Vorlage verwendet wird. Der Prozess wird dann wiederholt, wobei die zweite Vorlage (nach Transformationen) als Argument und die erste Vorlage in ihrer ursprünglichen Form als Parameter verwendet wird.
Die zur Bestimmung der Reihenfolge verwendeten Typen hängen vom Kontext ab:
- im Kontext eines Funktionsaufrufs sind die Typen jene Funktionsparametertypen, für die der Funktionsaufruf Argumente hat (Standardfunktionsargumente, Parameterpacks, (seit C++11) und Ellipsen-Parameter werden nicht berücksichtigt -- siehe Beispiele unten)
- im Kontext eines Aufrufs einer benutzerdefinierten Konvertierungsfunktion werden die Rückgabetypen der Konvertierungsfunktions-Templates verwendet
- in anderen Kontexten wird der Funktionstemplate-Typ verwendet
Jeder Typ aus der obigen Liste des Parameter-Templates wird abgeleitet. Vor Beginn der Ableitung wird jeder Parameter
P
des Parameter-Templates und das entsprechende Argument
A
des Argument-Templates wie folgt angepasst:
-
Wenn sowohl
Pals auchAzuvor Referenztypen waren, bestimme welcher stärker cv-qualifiziert ist (in allen anderen Fällen werden CV-Qualifizierer für partielle Ordnungszwecke ignoriert) -
Wenn
Pein Referenztyp ist, wird er durch den referenzierten Typ ersetzt -
Wenn
Aein Referenztyp ist, wird er durch den referenzierten Typ ersetzt -
Wenn
Pcv-qualifiziert ist, wirdPdurch die cv-unqualifizierte Version von sich selbst ersetzt -
Wenn
Acv-qualifiziert ist, wirdAdurch die cv-unqualifizierte Version von sich selbst ersetzt
Nach diesen Anpassungen erfolgt die Deduktion von
P
aus
A
gemäß
Template-Argument-Deduktion aus einem Typ
.
|
Wenn
Wenn
|
(since C++11) |
Wenn das Argument
A
der transformierten Template-1 verwendet werden kann, um den entsprechenden Parameter
P
von Template-2 abzuleiten, aber nicht umgekehrt, dann ist dieses
A
spezialisierter als
P
in Bezug auf die Typen, die durch dieses
P/A
-Paar abgeleitet werden.
Wenn die Deduktion in beide Richtungen erfolgreich ist und die ursprünglichen
P
und
A
Referenztypen waren, werden zusätzliche Tests durchgeführt:
-
Wenn
Aein Lvalue-Referenz war undPeine Rvalue-Referenz war, wirdAals spezialisierter alsPbetrachtet. -
Wenn
Astärker cv-qualifiziert war alsP, wirdAals spezialisierter alsPbetrachtet.
In allen anderen Fällen ist keines der Templates bezüglich des/der durch dieses
P/A
-Paar abgeleiteten Typs(en) spezialisierter als das andere.
Nachdem jedes
P
und
A
in beiden Richtungen betrachtet wurde, falls für jeden betrachteten Typ
- template-1 ist mindestens so spezialisiert wie template-2 für alle Typen
- template-1 ist spezialisierter als template-2 für einige Typen
- template-2 ist für keine Typen spezialisierter als template-1 ODER ist für keine Typen mindestens so spezialisiert
Dann ist Template-1 spezialisierter als Template-2. Wenn die obigen Bedingungen nach dem Vertauschen der Template-Reihenfolge wahr sind, dann ist Template-2 spezialisierter als Template-1. Andernfalls ist keines der Templates spezialisierter als das andere.
|
Bei Gleichstand, wenn eine Funktionsvorlage einen nachgestellten Parameterpack hat und die andere nicht, wird diejenige mit dem weggelassenen Parameter als spezialisierter betrachtet als die mit dem leeren Parameterpack. |
(since C++11) |
Wenn nach Betrachtung aller Paare überladener Templates eines vorhanden ist, das eindeutig spezialisierter als alle anderen ist, wird die Spezialisierung dieses Templates ausgewählt, andernfalls schlägt die Kompilierung fehl.
In den folgenden Beispielen werden die fiktiven Argumente U1, U2 genannt:
template<class T> void f(T); // Template #1 template<class T> void f(T*); // Template #2 template<class T> void f(const T*); // Template #3 void m() { const int* p; f(p); // Überladungsauflösung wählt: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // Partielle Ordnung: // #1 aus transformiertem #2: void(T) aus void(U1*): P=T A=U1*: Deduktion ok: T=U1* // #2 aus transformiertem #1: void(T*) aus void(U1): P=T* A=U1: Deduktion fehlgeschlagen // #2 ist spezialisierter als #1 bezüglich T // #1 aus transformiertem #3: void(T) aus void(const U1*): P=T, A=const U1*: ok // #3 aus transformiertem #1: void(const T*) aus void(U1): P=const T*, A=U1: fehlgeschlagen // #3 ist spezialisierter als #1 bezüglich T // #2 aus transformiertem #3: void(T*) aus void(const U1*): P=T* A=const U1*: ok // #3 aus transformiertem #2: void(const T*) aus void(U1*): P=const T* A=U1*: fehlgeschlagen // #3 ist spezialisierter als #2 bezüglich T // Ergebnis: #3 wird ausgewählt // mit anderen Worten: f(const T*) ist spezialisierter als f(T) oder f(T*) }
template<class T> void f(T, T*); // #1 template<class T> void f(T, int*); // #2 void m(int* p) { f(0, p); // Deduktion für #1: void f(T, T*) [T = int] // Deduktion für #2: void f(T, int*) [T = int] // Partielle Ordnung: // #1 von #2: void(T,T*) von void(U1,int*): P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: schlägt fehl // #2 von #1: void(T,int*) von void(U1,U2*): P1=T A1=U1: T=U1 // P2=int* A2=U2*: schlägt fehl // Keines ist bezüglich T spezialisierter, der Aufruf ist mehrdeutig }
template<class T> void g(T); // Template #1 template<class T> void g(T&); // Template #2 void m() { float x; g(x); // Deduktion von #1: void g(T ) [T = float] // Deduktion von #2: void g(T&) [T = float] // Partielle Ordnung: // #1 von #2: void(T) von void(U1&): P=T, A=U1 (nach Anpassung), ok // #2 von #1: void(T&) von void(U1): P=T (nach Anpassung), A=U1: ok // Keines ist bezüglich T spezialisierter, der Aufruf ist mehrdeutig }
template<class T> struct A { A(); }; template<class T> void h(const T&); // #1 template<class T> void h(A<T>&); // #2 void m() { A<int> z; h(z); // Ableitung von #1: void h(const T &) [T = A<int>] // Ableitung von #2: void h(A<T> &) [T = int] // Partielle Ordnung: // #1 von #2: void(const T&) von void(A<U1>&): P=T A=A<U1>: ok T=A<U1> // #2 von #1: void(A<T>&) von void(const U1&): P=A<T> A=const U1: scheitert // #2 ist spezialisierter als #1 bezüglich T const A<int> z2; h(z2); // Ableitung von #1: void h(const T&) [T = A<int>] // Ableitung von #2: void h(A<T>&) [T = int], aber Substitution scheitert // Nur eine Überladung zur Auswahl, partielle Ordnung nicht versucht, #1 wird aufgerufen }
Da ein Aufrufkontext nur Parameter berücksichtigt, für die es explizite Aufrufargumente gibt, werden Funktionsparameter-Packs, (since C++11) Ellipsen-Parameter und Parameter mit Standardargumenten, für die kein explizites Aufrufargument vorhanden ist, ignoriert:
template<class T> void f(T); // #1 template<class T> void f(T*, int = 1); // #2 void m(int* ip) { int* ip; f(ip); // ruft #2 auf (T* ist spezialisierter als T) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // ruft #2 auf (T* ist spezialisierter als T) }
template<class T, class U> struct A {}; template<class T, class U> void f(U, A<U, T>* p = 0); // #1 template<class U> void f(U, A<U, U>* p = 0); // #2 void h() { f<int>(42, (A<int, int>*)0); // ruft #2 auf f<int>(42); // Fehler: mehrdeutig }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // Fehler: mehrdeutig }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // ruft #2 auf aufgrund des Tie-Breakers zwischen Parameter-Pack und keinem Parameter // (Hinweis: war zwischen DR692 und DR1395 mehrdeutig) }
template<class T, class... U> void g(T*, U...); // #1 template<class T> void g(T); // #2 void h(int i) { g(&i); // OK: ruft #1 auf (T* ist spezialisierter als T) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // OK: wählt #2; nicht-variadisches Template ist spezialisierter als // variadisches Template (war vor DR1395 mehrdeutig, da Deduktion // in beide Richtungen fehlschlug)
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // ruft #1 auf f(1, 2, 3); // ruft #2 auf f(1, 2); // ruft #3 auf; nicht-variadisches Template #3 ist // spezialisierter als die variadischen Templates #1 und #2
Während der Template-Argumentableitung im partiellen Ordnungsprozess müssen Template-Parameter nicht mit Argumenten abgeglichen werden, wenn das Argument in keinem der für die partielle Ordnung betrachteten Typen verwendet wird
template<class T> T f(int); // #1 template<class T, class U> T f(U); // #2 void g() { f<int>(1); // Spezialisierung von #1 ist explizit: T f(int) [T = int] // Spezialisierung von #2 wird abgeleitet: T f(U) [T = int, U = int] // Partielle Ordnung (nur unter Berücksichtigung des Argumenttyps): // #1 von #2: T(int) von U1(U2): schlägt fehl // #2 von #1: T(U) von U1(int): ok: U=int, T unbenutzt // ruft #1 auf }
|
Die partielle Ordnung von Funktions-Templates, die Template-Parameter-Packs enthalten, ist unabhängig von der Anzahl der abgeleiteten Argumente für diese Template-Parameter-Packs. template<class...> struct Tuple {}; template<class... Types> void g(Tuple<Types...>); // #1 template<class T1, class... Types> void g(Tuple<T1, Types...>); // #2 template<class T1, class... Types> void g(Tuple<T1, Types&...>); // #3 g(Tuple<>()); // calls #1 g(Tuple<int, float>()); // calls #2 g(Tuple<int, float&>()); // calls #3 g(Tuple<int>()); // calls #3 |
(seit C++11) |
|
Dieser Abschnitt ist unvollständig
Grund: 14.8.3[temp.over] |
Um einen Aufruf einer Funktionsvorlage zu kompilieren, muss der Compiler zwischen Nicht-Vorlagen-Überladungen, Vorlagen-Überladungen und den Spezialisierungen der Vorlagen-Überladungen entscheiden.
template<class T> void f(T); // #1: Template-Überladung template<class T> void f(T*); // #2: Template-Überladung void f(double); // #3: Nicht-Template-Überladung template<> void f(int); // #4: Spezialisierung von #1 f('a'); // ruft #1 auf f(new int(1)); // ruft #2 auf f(1.0); // ruft #3 auf f(1); // ruft #4 auf
Funktionsüberladungen vs. Funktionsspezialisierungen
Beachten Sie, dass nur Nicht-Template- und Primär-Template-Überladungen an der Überladungsauflösung teilnehmen. Die Spezialisierungen sind keine Überladungen und werden nicht berücksichtigt. Erst nachdem die Überladungsauflösung die am besten passende primäre Funktionsvorlage ausgewählt hat, werden deren Spezialisierungen untersucht, um festzustellen, ob eine besser passt.
template<class T> void f(T); // #1: Überladung für alle Typen template<> void f(int*); // #2: Spezialisierung von #1 für Zeiger auf int template<class T> void f(T*); // #3: Überladung für alle Zeigertypen f(new int(1)); // ruft #3 auf, obwohl die Spezialisierung von #1 eine perfekte Übereinstimmung wäre
Es ist wichtig, diese Regel beim Anordnen der Header-Dateien einer Übersetzungseinheit zu beachten. Für weitere Beispiele des Zusammenspiels zwischen Funktionsüberladungen und Funktionsspezialisierungen, erweitern Sie unten:
| Beispiele |
|---|
|
Betrachten wir zunächst einige Szenarien, in denen die argumentabhängige Suche nicht angewendet wird. Dazu verwenden wir den Aufruf ( f ) ( t ) . Wie unter ADL beschrieben, unterdrückt das Einklammern des Funktionsnamens die argumentabhängige Suche.
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // Überladung #1 vor f() POR template<class T> void f(T*) { std::cout << "#2\n"; } // Überladung #2 vor f() POR template<class T> void g(T* t) { (f)(t); // f() POR } int main() { A* p = nullptr; g(p); // POR von g() und f() } // Sowohl #1 als auch #2 werden zur Kandidatenliste hinzugefügt; // #2 wird ausgewählt, da es besser passt. Ausgabe: #2
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // Only #1 is added to the candidate list; #2 is defined after POR; // therefore, it is not considered for overloading even if it is a better match. Ausgabe: #1
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list; #3 is a better match defined after POR. The // candidate list consists of #1 which is eventually selected. After that, the explicit // specialization #3 of #1 declared after POI is selected because it is a better match. // This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL. Ausgabe: #3
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { (f)(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 ist das einzige Mitglied der Kandidatenliste und wird letztendlich ausgewählt. // Danach wird die explizite Spezialisierung #3 übersprungen, da sie tatsächlich // #2 spezialisiert, das nach POR deklariert wurde. Ausgabe: #1
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected being the better match. Ausgabe: #2
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<> void f<>(A*) { std::cout << "#3\n"; } // #3 template<class T> void f(T*) { std::cout << "#2\n"; } // #2 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 is added to the candidate list as a result of the ordinary lookup; // #2 is defined after POR but it is added to the candidate list via ADL lookup. // #2 is selected among the primary templates, being the better match. // Since #3 is declared before #2, it is an explicit specialization of #1. // Hence the final selection is #2. Ausgabe: #2
Diesen Code ausführen
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // #1 template<class T> void g(T* t) { f(t); // f() POR } template<class T> void f(T*) { std::cout << "#2\n"; } // #2 template<> void f<>(A*) { std::cout << "#3\n"; } // #3 int main() { A* p = nullptr; g(p); // POR of g() and f() } // #1 wird als Ergebnis der gewöhnlichen Namenssuche zur Kandidatenliste hinzugefügt; // #2 wird nach POR definiert, aber über ADL-Suche zur Kandidatenliste hinzugefügt. // #2 wird unter den Primärtemplates als die bessere Übereinstimmung ausgewählt. // Da #3 nach #2 deklariert ist, handelt es sich um eine explizite Spezialisierung von #2; // daher wird sie als aufzurufende Funktion ausgewählt. Ausgabe: #3
|
Für detaillierte Regeln zur Überladungsauflösung siehe overload resolution .
Funktions-Template-Spezialisierung
|
Dieser Abschnitt ist unvollständig
Grund: 14.8[temp.fct.spec] (beachten Sie, dass 14.8.1[temp.arg.explicit] bereits im Artikel über vollständige Spezialisierung enthalten ist: entweder gehören die Funktionsspezifika hierher: Fehlen von partiellen, Interaktion mit Funktionsüberladungen, oder einfach darauf verweisen |
Schlüsselwörter
template , extern (seit C++11)
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 214 | C++98 | das genaue Verfahren des partiellen Orderings war nicht spezifiziert | Spezifikation hinzugefügt |
| CWG 532 | C++98 |
die Reihenfolge zwischen einer nicht-statischen Member-Funktionsvorlage
und einer Nicht-Member-Funktionsvorlage war nicht spezifiziert |
Spezifikation hinzugefügt |
| CWG 581 | C++98 |
Template-Argumentliste in einer expliziten Spezialisierung oder
Instanziierung einer Konstruktorvorlage war erlaubt |
verboten |
| CWG 1321 | C++98 |
es war unklar, ob gleiche abhängige Namen in der
ersten Deklaration und einer Neudeklaration äquivalent sind |
sie sind äquivalent und
die Bedeutung ist gleich wie in der ersten Deklaration |
| CWG 1395 | C++11 |
Deduktion fehlgeschlagen wenn A von einem Pack stammte,
und es keinen Empty-Pack-Tie-Breaker gab |
Deduktion erlaubt,
Tie-Breaker hinzugefügt |
| CWG 1406 | C++11 |
der Typ des neuen ersten Parameters, der für
eine nicht-statische Member-Funktionsvorlage hinzugefügt wurde, war nicht relevant für den Ref-Qualifier dieser Vorlage |
der Typ ist ein Rvalue-
Referenztyp wenn der Ref-Qualifier
&&
ist
|
| CWG 1446 | C++11 |
der Typ des neuen ersten Parameters, der für eine nicht-statische Member-
Funktionsvorlage ohne Ref-Qualifier hinzugefügt wurde, war ein Lvalue-Referenz- typ, selbst wenn diese Member-Funktionsvorlage mit einer Funktionsvorlage verglichen wurde, deren erster Parameter Rvalue-Referenztyp hat |
der Typ ist ein
Rvalue-Referenz- typ in diesem Fall |
| CWG 2373 | C++98 |
neue erste Parameter wurden zu den Parameterlisten
von statischen Member-Funktionsvorlagen im partiellen Ordering hinzugefügt |
nicht hinzugefügt |