Funktionsschablone
Eine Funktionsvorlage definiert eine Familie von Funktionen.
[bearbeiten] Syntax
template < parameter-liste > funktionsdeklaration |
(1) | ||||||||
template < parameter-liste > requires bedingung funktionsdeklaration |
(2) | (seit C++20) | |||||||
| funktionsdeklaration-mit-platzhaltern | (3) | (seit C++20) | |||||||
export template < parameter-liste > funktionsdeklaration |
(4) | (in C++11 entfernt) | |||||||
[bearbeiten] Erklärung
| parameter-liste | - | eine nicht-leere, durch Kommas getrennte Liste der Vorlagenparameter, von denen jeder entweder ein Nicht-Typ-Parameter, ein Typ-Parameter, ein Vorlagen-Parameter oder ein Parameter-Pack von einem dieser ist(seit C++11). Wie bei jeder Vorlage können Parameter eingeschränkt sein(seit C++20) |
| funktionsdeklaration | - | eine Funktionsdeklaration. Der deklarierte Funktionsname wird zu einem Vorlagennamen. |
| constraint | - | ein Bedingungsausdruck, der die von dieser Funktionsvorlage akzeptierten Vorlagenparameter einschränkt |
| funktionsdeklaration- mit-platzhaltern |
- | eine Funktionsdeklaration, bei der der Typ mindestens eines Parameters den Platzhalter auto oder Concept auto verwendet: Die Vorlagenparameterliste erhält einen erfundenen Parameter für jeden Platzhalter (siehe Abgekürzte Funktionsvorlagen unten) |
|
|
(bis C++11) |
Abgekürzte FunktionstemplatesWenn Platzhaltertypen (entweder auto oder Concept auto) in der Parameterliste einer Funktionsdeklaration oder einer Funktionsvorlagendeklaration erscheinen, deklariert die Deklaration eine Funktionsvorlage, und ein erfundener Vorlagenparameter für jeden Platzhalter wird an die Vorlagenparameterliste 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 Funktionsvorlagen können wie alle Funktionsvorlagen spezialisiert werden. template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
|
(seit C++20) |
[bearbeiten] Signatur einer Funktionsvorlage
Jede Funktionsvorlage hat eine Signatur.
Die Signatur eines template-head ist die Vorlagenparameterliste, ohne die Namen der Vorlagenparameter und Standardargumente, und die requires-Klausel (falls vorhanden)(seit C++20).
Die Signatur einer Funktionsvorlage enthält den Namen, die Parameter-Typ-Liste, den Rückgabetyp, die nachfolgende 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 Klassen-Member ist, enthält ihre Signatur die Klasse, deren Member die Funktion ist, anstelle des umschließenden Namensraums. Ihre Signatur enthält auch die nachfolgende requires-Klausel (falls vorhanden)(seit C++20), den ref-Qualifizierer (falls vorhanden) und(seit C++11) die cv-Qualifizierer (falls vorhanden).
|
Wenn die Funktionsvorlage eine friend-Funktion mit einer Bedingung ist, die umschließende Vorlagenparameter betrifft, enthält ihre Signatur die umschließende Klasse anstelle des umschließenden Namensraums. |
(seit C++20) |
[bearbeiten] Instanziierung einer Funktionsvorlage
Eine Funktionsvorlage ist für sich allein kein Typ und keine Funktion. Aus einer Quelldatei, die nur Vorlagendefinitionen enthält, wird kein Code generiert. Damit überhaupt Code erscheint, 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.
[bearbeiten] Explizite Instanziierung
template rückgabetyp name < argumentliste > ( parameterliste ) ; |
(1) | ||||||||
template rückgabetyp name ( parameterliste ) ; |
(2) | ||||||||
extern template rückgabetyp name < argumentliste > ( parameterliste ) ; |
(3) | (seit C++11) | |||||||
extern template rückgabetyp name ( parameterliste ) ; |
(4) | (seit C++11) | |||||||
Eine explizite Instanziierungsdefinition erzwingt die Instanziierung der Funktion oder der Memberfunktion, auf die sie sich bezieht. Sie kann im Programm überall nach der Vorlagendefinition erscheinen und darf für eine gegebene Argumentliste nur einmal im Programm vorkommen, ohne dass eine Diagnose erforderlich ist.
|
Eine explizite Instanziierungsdeklaration (eine externe Vorlage) verhindert implizite Instanziierungen: Der Code, der andernfalls eine implizite Instanziierung verursachen würde, muss die explizite Instanziierungsdefinition verwenden, die irgendwo anders im Programm bereitgestellt wird. |
(seit C++11) |
Ein nachfolgendes Vorlagenargument kann in einer expliziten Instanziierung einer Spezialisierung einer Funktionsvorlage oder einer Memberfunktionsvorlage weggelassen werden, wenn es aus dem Funktionsparameter abgeleitet werden kann.
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // instantiates f<double>(double) template void f<>(char); // instantiates f<char>(char), template argument deduced template void f(int); // instantiates f<int>(int), template argument deduced
Die explizite Instanziierung einer Funktionsvorlage oder einer Memberfunktion einer Klassenvorlage kann inline oder constexpr nicht verwenden. Wenn die Deklaration der expliziten Instanziierung eine implizit deklarierte spezielle Memberfunktion benennt, ist das Programm fehlerhaft.
Die explizite Instanziierung eines Konstruktors kann keine Vorlagenparameterliste (Syntax (1)) verwenden, was auch nie notwendig ist, da sie abgeleitet werden können (Syntax (2)).
|
Die explizite Instanziierung eines prospektiven Destruktors muss den ausgewählten Destruktor der Klasse benennen. |
(seit C++20) |
Explizite Instanziierungsdeklarationen unterdrücken nicht die implizite Instanziierung von inline-Funktionen, auto-Deklarationen, Referenzen und Klassenvorlagenspezialisierungen. (Somit wird, wenn die Inline-Funktion, die Gegenstand der expliziten Instanziierungsdeklaration ist, ODR-verwendet wird, sie für Inlining implizit instanziiert, aber ihre Out-of-line-Kopie wird in dieser Translationseinheit nicht generiert.)
Die 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 even though &p isn’t an int.
[bearbeiten] Implizite Instanziierung
Wenn Code auf eine Funktion in einem Kontext zugreift, der die Funktionsdefinition erfordert, oder wenn die Existenz der Definition die Semantik des Programms beeinflusst(seit C++11), und diese spezielle Funktion nicht explizit instanziiert wurde, tritt eine implizite Instanziierung auf. Die Liste der Vorlagenargumente 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); // instantiates and calls f<double>(double) f<>('a'); // instantiates and calls f<char>(char) f(7); // instantiates and calls f<int>(int) void (*pf)(std::string) = f; // instantiates f<string>(string) pf("∇"); // calls f<string>(string) }
|
Die Existenz einer Funktionsdefinition gilt als Einflussnahme auf die Semantik des Programms, wenn die Funktion für die Konstantenbewertung durch einen Ausdruck benötigt wird, selbst wenn die Konstantenbewertung des Ausdrucks nicht erforderlich ist oder wenn die Konstanten-Ausdrucksbewertung 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 is not potentially constant evaluated h<false, int>(0); // error: instantiates f<int> even though B evaluates to false // and list-initialization of int from int cannot be narrowing } |
(seit C++11) |
Hinweis: Das vollständige Weglassen von <> erlaubt der Überladungsauflösung, sowohl Vorlagen- als auch Nicht-Vorlagenüberladungen zu prüfen.
[bearbeiten] Ableitung von Vorlagenargumenten
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); // calls convert<int,double>(double) char c = convert<char>(d); // calls convert<char,double>(double) int(*ptr)(float) = convert; // instantiates convert<int, float>(float) }
Dieser Mechanismus macht es möglich, Vorlagenoperatoren zu verwenden, da es keine Syntax gibt, um Vorlagenargumente für einen Operator anzugeben, außer durch Umschreiben als Funktionsaufrufausdruck.
Die Ableitung von Vorlagenargumenten erfolgt nach der Namenssuche für die Funktionsvorlage (die eine argumentabhängige Suche beinhalten kann) und vor der Überladungsauflösung.
Siehe Ableitung von Vorlagenargumenten für Details.
[bearbeiten] Explizite Vorlagenargumente
Vorlagenargumente einer Funktionsvorlage können erhalten werden aus
- Ableitung von Vorlagenargumenten
- Standard-Vorlagenargumente
- explizit angegeben, was in den folgenden Kontexten geschehen kann
- in einem Funktionsaufrufausdruck
- wenn eine Adresse einer Funktion genommen wird
- wenn eine Referenz auf eine Funktion initialisiert wird
- wenn ein Zeiger auf eine Memberfunktion gebildet wird
- in einer expliziten Spezialisierung
- in einer expliziten Instanziierung
- in einer friend-Deklaration
Es gibt keine Möglichkeit, Vorlagenargumente für überladene Operatoren, Konvertierungsfunktionen und Konstruktoren explizit anzugeben, da diese ohne Verwendung des Funktionsnamens aufgerufen werden.
Die angegebenen Vorlagenargumente müssen den Vorlagenparametern in ihrer Art entsprechen (d. h. Typ für Typ, Nicht-Typ für Nicht-Typ und Vorlage für Vorlage). Es dürfen nicht mehr Argumente als Parameter vorhanden sein (es sei denn, ein Parameter ist ein Parameter-Pack, in diesem Fall muss für jeden Nicht-Pack-Parameter ein Argument vorhanden sein)(seit C++11).
Die angegebenen Nicht-Typ-Argumente müssen entweder den Typen der entsprechenden Nicht-Typ-Vorlagenparameter entsprechen oder in sie konvertierbar sein.
Die Funktionsparameter, die nicht an der Ableitung von Vorlagenargumenten teilnehmen (z. B. wenn die entsprechenden Vorlagenargumente explizit angegeben werden), unterliegen impliziten Konvertierungen in den Typ des entsprechenden Funktionsparameters (wie bei der üblichen Überladungsauflösung).
|
Ein Vorlagenparameter-Pack, das explizit angegeben wird, kann durch Ableitung von Vorlagenargumenten 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) |
[bearbeiten] Substitution von Vorlagenargumenten
Wenn alle Vorlagenargumente angegeben, abgeleitet oder aus Standard-Vorlagenargumenten erhalten wurden, wird jede Verwendung eines Vorlagenparameters in der Funktionsparameterliste durch die entsprechenden Vorlagenargumente ersetzt.
Substitutionsfehler (d. h. Fehler beim Ersetzen von Vorlagenparametern durch die abgeleiteten oder bereitgestellten Vorlagenargumente) einer Funktionsvorlage entfernt die Funktionsvorlage aus der Überladungsmenge. Dies ermöglicht eine Reihe von Möglichkeiten zur Manipulation von Überladungsmengen mittels Vorlagenmetaprogrammierung: siehe SFINAE für Details.
Nach der Substitution werden alle Funktionsparameter vom Array- und Funktionstyp zu Zeigern und alle Top-Level-cv-Qualifizierer von Funktionsparametern fallen gelassen (wie bei einer regulären Funktionsdeklaration).
Das Entfernen der Top-Level-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); // two different functions with the same type, but // within the function, t has different cv qualifications f<int>(1); // function type is void(int), t is int f<const int>(1); // function type is void(int), t is const int // two different functions with the same type and the same x // (pointers to these two functions are not equal, // and function-local statics would have different addresses) g<int>(1); // function type is void(int), x is const int g<const int>(1); // function type is void(int), x is const int // only top-level cv-qualifiers are dropped: h<const int>(1, NULL); // function type is void(int, const int*) // z is const int, zp is const int*
[bearbeiten] Überladung von Funktionsvorlagen
Funktionsvorlagen und Nicht-Vorlagenfunktionen können überladen werden.
Eine Nicht-Vorlagenfunktion ist immer von einer Vorlagenspezialisierung mit demselben Typ verschieden. Spezialisierungen verschiedener Funktionsvorlagen sind immer voneinander verschieden, auch wenn sie denselben Typ haben. Zwei Funktionsvorlagen mit demselben Rückgabetyp und derselben Parameterliste sind unterschiedlich und können durch ihre explizite Vorlagenargumentliste unterschieden werden.
Wenn ein Ausdruck, der Typ- oder Nicht-Typ-Vorlagenparameter verwendet, in der Funktionsparameterliste oder im Rückgabetyp erscheint, bleibt dieser Ausdruck für die Überladung Teil der Signatur der Funktionsvorlage.
template<int I, int J> A<I+J> f(A<I>, A<J>); // overload #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // same as #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // overload #2
Zwei Ausdrücke, die Vorlagenparameter beinhalten, werden als äquivalent bezeichnet, wenn zwei Funktionsdefinitionen, die diese Ausdrücke enthalten, unter ODR gleich wären, d. h., die beiden Ausdrücke enthalten dieselbe Abfolge von Token, deren Namen durch Namenssuche denselben Entitäten zugeordnet werden, außer dass Vorlagenparameter 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 overload #1 template<int K, int L> void f(A<K+L>); // equivalent to #1
Bei der Bestimmung, ob zwei abhängige Ausdrücke äquivalent sind, werden nur die beteiligten abhängigen Namen betrachtet, 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())) is a dependent type int g(int); template<class T> decltype(g(T())) h() { // redeclaration of h() uses earlier lookup return g(T()); // although the lookup here does find g(int) } int i = h<int>(); // template argument substitution fails; g(int) // was not in scope at the first declaration of h()
Zwei Funktionsvorlagen gelten als äquivalent, wenn
- sie im selben Gültigkeitsbereich deklariert sind
- sie denselben Namen haben
- sie *äquivalente* Vorlagenparameterlisten haben, d. h. die Listen sind gleich lang, und für jedes entsprechende Parameterpaar gilt Folgendes:
- die beiden Parameter sind von derselben Art (beide Typen, beide Nicht-Typen oder beide Vorlagen)
|
(seit C++11) |
- wenn Nicht-Typ, ihre Typen sind äquivalent;
- wenn Vorlage, ihre Vorlagenparameter sind äquivalent;
|
(seit C++20) |
- die Ausdrücke, die Vorlagenparameter in ihren Rückgabetypen und Parameterlisten beinhalten, *äquivalent* sind
|
(seit C++20) |
Zwei potenziell ausgewertete(seit C++20) Ausdrücke, die Vorlagenparameter beinhalten, werden als *funktional äquivalent* bezeichnet, wenn sie nicht *äquivalent* sind, aber für eine beliebige gegebene Menge von Vorlagenargumenten die Auswertung der beiden Ausdrücke zum selben Wert führt.
Zwei Funktionsvorlagen gelten als *funktional äquivalent*, wenn sie *äquivalent* sind, abgesehen davon, dass ein oder mehrere Ausdrücke, die Vorlagenparameter in ihren Rückgabetypen und Parameterlisten beinhalten, *funktional äquivalent* sind.
|
Darüber hinaus sind zwei Funktionsvorlagen *funktional äquivalent*, aber nicht *äquivalent*, wenn ihre Bedingungen unterschiedlich spezifiziert sind, sie aber die gleichen Listen von Vorlagenargumenten akzeptieren und erfüllen. |
(seit C++20) |
Wenn ein Programm Deklarationen von Funktionsvorlagen enthält, die *funktional äquivalent*, aber nicht *äquivalent* sind, ist das Programm fehlerhaft; keine Diagnose erforderlich.
// equivalent template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+10>); // redeclaration of overload #1 // not equivalent template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+11>); // overload #2 // functionally-equivalent but not equivalent // This program is ill-formed, no diagnostic required template<int I> void f(A<I>, A<I+10>); // overload #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // functionally equivalent
Wenn dieselbe Spezialisierung einer Funktionsvorlage mehr als eine überladene Funktionsvorlage trifft (was oft aus der Ableitung von Vorlagenargumenten resultiert), wird eine *partielle Ordnung von überladenen Funktionsvorlagen* 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", dass "A weniger Typen akzeptiert als B".
Formal betrachtet, um zu bestimmen, welche von zwei Funktionsvorlagen spezialisierter ist, transformiert der partielle Ordnungsprozess zunächst eine der beiden Vorlagen wie folgt:
- Für jeden Typ-, Nicht-Typ- und Vorlagenparameter, einschließlich Parameter-Packs,(seit C++11) wird ein eindeutiger fiktiver Typ, Wert oder eine Vorlage generiert und in den Funktionstyp der Vorlage substituiert.
- Wenn nur eine der beiden verglichenen Funktionsvorlagen eine Memberfunktion ist und diese Funktionsvorlage eine Nicht-Static-Member einer Klasse
Aist, wird ihrer Parameterliste ein neuer erster Parameter hinzugefügt. Gegeben sei cv als die cv-Qualifizierer der Funktionsvorlage und ref als der ref-Qualifizierer der Funktionsvorlage(seit C++11), ist der neue Parametertyp cvA&, es sei denn, ref ist&&oder ref ist nicht vorhanden und der erste Parameter der anderen Vorlage hat den Typ einer Rvalue-Referenz. In diesem Fall ist der Typ cvA&&(seit C++11). Dies hilft bei der 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 deduction for int B<A>::operator*(R&) gives R=A // for int operator*(T&, R&), T=B<A>, R=A // For the purpose of partial ordering, the member template B<A>::operator* // is transformed into template<class R> int operator*(B<A>&, R&); // partial ordering between // int operator*( T&, R&) T=B<A>, R=A // and int operator*(B<A>&, R&) R=A // selects int operator*(B<A>&, A&) as more specialized }
Nachdem eine der beiden Vorlagen wie oben beschrieben transformiert wurde, wird die Ableitung von Vorlagenargumenten durchgefü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 Ordnung verwendeten Typen hängen vom Kontext ab:
- im Kontext eines Funktionsaufrufs sind die Typen die Funktionsparametertypen, für die der Funktionsaufruf Argumente hat (Standard-Funktionsargumente, Parameter-Packs,(seit C++11) und Ellipsenparameter werden nicht berücksichtigt - siehe Beispiele unten)
- im Kontext eines Aufrufs einer benutzerdefinierten Konvertierungsfunktion werden die Rückgabetypen der Konvertierungsfunktionsvorlagen verwendet.
- in anderen Kontexten wird der Typ der Funktionsvorlage verwendet.
Jeder Typ aus der obigen Liste der Parameter-Vorlage wird abgeleitet. Bevor die Ableitung beginnt, wird jeder Parameter P der Parameter-Vorlage und das entsprechende Argument A der Argument-Vorlage wie folgt angepasst:
- Wenn sowohl
Pals auchAvor dem Aufruf Referenztypen waren, wird bestimmt, welcher von beiden stärker cv-qualifiziert ist (in allen anderen Fällen werden cv-Qualifizierungen für die partielle Ordnung 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 davon ersetzt. - Wenn
Acv-qualifiziert ist, wirdAdurch die cv-unqualifizierte Version davon ersetzt.
Nach diesen Anpassungen erfolgt die Ableitung von P aus A gemäß Ableitung von Vorlagenargumenten aus einem Typ.
|
Wenn Wenn |
(seit C++11) |
Wenn das Argument A der transformierten Vorlage-1 verwendet werden kann, um den entsprechenden Parameter P der Vorlage-2 abzuleiten, aber nicht umgekehrt, dann ist dieses A spezialisierter als P in Bezug auf den/die durch dieses P/A-Paar abgeleiteten Typ(en).
Wenn die Ableitung in beiden Richtungen erfolgreich ist und die ursprünglichen P und A Referenztypen waren, werden zusätzliche Tests durchgeführt:
- Wenn
Aeine Lvalue-Referenz war undPeine Rvalue-Referenz, wirdAals spezialisierter alsPbetrachtet. - Wenn
Astärker cv-qualifiziert war alsP, wirdAals spezialisierter alsPbetrachtet.
In allen anderen Fällen ist keine Vorlage spezialisierter als die andere in Bezug auf den/die durch dieses P/A-Paar abgeleiteten Typ(en).
Nachdem jeder P und A in beiden Richtungen betrachtet wurde, gilt Folgendes, wenn für jeden betrachteten Typ gilt:
- Vorlage-1 ist für alle Typen mindestens so spezialisiert wie Vorlage-2
- Vorlage-1 ist für einige Typen spezialisierter als Vorlage-2
- Vorlage-2 ist für keinen Typ spezialisierter als Vorlage-1 ODER ist für keinen Typ mindestens so spezialisiert
Dann ist Vorlage-1 spezialisierter als Vorlage-2. Wenn die obigen Bedingungen nach Vertauschen der Vorlagenreihenfolge zutreffen, dann ist Vorlage-2 spezialisierter als Vorlage-1. Andernfalls ist keine Vorlage spezialisierter als die andere.
|
Bei einem Gleichstand, wenn eine Funktionsvorlage einen nachfolgenden Parameter-Pack hat und die andere nicht, gilt die mit dem weggelassenen Parameter als spezialisierter als die mit dem leeren Parameter-Pack. |
(seit C++11) |
Wenn nach der Betrachtung aller Paare von überladenen Vorlagen eine eindeutig spezialisierter ist als alle anderen, wird die Spezialisierung dieser Vorlage 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); // overload resolution picks: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // partial ordering: // #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1* // #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails // #2 is more specialized than #1 with regards to T // #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok // #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails // #3 is more specialized than #1 with regards to T // #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok // #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails // #3 is more specialized than #2 with regards to T // result: #3 is selected // in other words, f(const T*) is more specialized than f(T) or 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); // deduction for #1: void f(T, T*) [T = int] // deduction for #2: void f(T, int*) [T = int] // partial ordering: // #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: fails // #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1 // P2=int* A2=U2*: fails // neither is more specialized w.r.t T, the call is ambiguous }
template<class T> void g(T); // template #1 template<class T> void g(T&); // template #2 void m() { float x; g(x); // deduction from #1: void g(T ) [T = float] // deduction from #2: void g(T&) [T = float] // partial ordering: // #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok // #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok // neither is more specialized w.r.t T, the call is ambiguous }
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); // deduction from #1: void h(const T &) [T = A<int>] // deduction from #2: void h(A<T> &) [T = int] // partial ordering: // #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1>: ok T=A<U1> // #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1: fails // #2 is more specialized than #1 w.r.t T const A<int> z2; h(z2); // deduction from #1: void h(const T&) [T = A<int>] // deduction from #2: void h(A<T>&) [T = int], but substitution fails // only one overload to choose from, partial ordering not tried, #1 is called }
Da ein Aufrufkontext nur Parameter berücksichtigt, für die es explizite Aufrufargumente gibt, werden diese Funktionsparameter-Packs,(seit C++11) Ellipsenparameter 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); // calls #2 (T* is more specialized than T) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // calls #2 (T* is more specialized than 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); // calls #2 f<int>(42); // error: ambiguous }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // error: ambiguous }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // calls #2 due to the tie-breaker between parameter pack and no parameter // (note: was ambiguous between DR692 and DR1395) }
template<class T, class... U> void g(T*, U...); // #1 template<class T> void g(T); // #2 void h(int i) { g(&i); // OK: calls #1 (T* is more specialized than T) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // OK: selects #2; non-variadic template is more specialized than // variadic template (was ambiguous before DR1395 because deduction // failed in both directions)
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(); // calls #1 f(1, 2, 3); // calls #2 f(1, 2); // calls #3; non-variadic template #3 is more // specialized than the variadic templates #1 and #2
Bei der Ableitung von Vorlagenargumenten innerhalb des Prozesses der partiellen Ordnung müssen Vorlagenparameter nicht mit Argumenten übereinstimmen, 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); // specialization of #1 is explicit: T f(int) [T = int] // specialization of #2 is deduced: T f(U) [T = int, U = int] // partial ordering (only considering the argument type): // #1 from #2: T(int) from U1(U2): fails // #2 from #1: T(U) from U1(int): ok: U=int, T unused // calls #1 }
|
Die partielle Ordnung von Funktionsvorlagen, die Vorlagenparameter-Packs enthalten, ist unabhängig von der Anzahl der für diese Vorlagenparameter-Packs abgeleiteten Argumente. 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 overload template<class T> void f(T*); // #2: template overload void f(double); // #3: non-template overload template<> void f(int); // #4: specialization of #1 f('a'); // calls #1 f(new int(1)); // calls #2 f(1.0); // calls #3 f(1); // calls #4
[bearbeiten] Überladungen von Funktionen vs. Spezialisierungen von Funktionen
Beachten Sie, dass nur Nicht-Vorlagen- und Primärvorlagen-Ü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 ihre Spezialisierungen geprüft, um zu sehen, ob eine besser passt.
template<class T> void f(T); // #1: overload for all types template<> void f(int*); // #2: specialization of #1 for pointers to int template<class T> void f(T*); // #3: overload for all pointer types f(new int(1)); // calls #3, even though specialization of #1 would be a perfect match
Es ist wichtig, diese Regel beim Ordnen der Header-Dateien einer Translationseinheit zu beachten. Für weitere Beispiele des Zusammenspiels zwischen Funktionsüberladungen und Funktionsspezialisierungen, unten erweitern.
| Beispiele |
|---|
|
Betrachten wir zunächst einige Szenarien, in denen die argumentabhängige Suche nicht verwendet wird. Dazu verwenden wir den Aufruf (f)(t). Wie in ADL beschrieben, unterdrückt das Einpacken des Funktionsnamens in Klammern die argumentabhängige Suche.
Führen Sie diesen Code aus #include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // overload #1 before f() POR template<class T> void f(T*) { std::cout << "#2\n"; } // overload #2 before f() POR template<class T> void g(T* t) { (f)(t); // f() POR } int main() { A* p = nullptr; g(p); // POR of g() and f() } // Both #1 and #2 are added to the candidate list; // #2 is selected because it is a better match. Ausgabe #2
Führen Sie diesen Code aus #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
Führen Sie diesen Code aus #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
Führen Sie diesen Code aus #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 is the only member of the candidate list and it is eventually selected. // After that, the explicit specialization #3 is skipped because it actually // specializes #2 declared after POR. Ausgabe #1
Führen Sie diesen Code aus #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
Führen Sie diesen Code aus #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
Führen Sie diesen Code aus #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 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 after #2, it is an explicit specialization of #2; // therefore, selected as the function to call. Ausgabe #3
|
Für detaillierte Regeln zur Überladungsauflösung siehe Überladungsauflösung.
[bearbeiten] Spezialisierung von Funktionsvorlagen
| 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 Spezialisierungen steht: entweder Funktionsspezifika hierher: Fehlen von partiellen, Interaktion mit Funktionsüberladungen, oder einfach darauf verweisen. |
[bearbeiten] Schlüsselwörter
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 214 | C++98 | das genaue Verfahren der partiellen Ordnung war nicht spezifiziert | Spezifikation hinzugefügt |
| CWG 532 | C++98 | die Reihenfolge zwischen einer Nicht-Static-Member-Funktionsvorlage und einer Nicht-Member-Funktionsvorlage war nicht spezifiziert |
Spezifikation hinzugefügt |
| CWG 581 | C++98 | die Vorlagenargumentliste in einer expliziten Spezialisierung oder Instanziierung einer Konstruktorvorlage war erlaubt |
verbieten |
| CWG 1321 | C++98 | war unklar, ob gleiche abhängige Namen in der erste Deklaration und eine Neu-Deklaration sind äquivalent |
sie sind äquivalent und die Bedeutung ist dieselbe wie in der ersten Deklaration |
| CWG 1395 | C++11 | Deduktion fehlgeschlagen, wenn A aus einem Pack kam, und es gab keinen leeren Pack-Tiebreaker |
Deduktion erlaubt, Tiebreaker hinzugefügt |
| CWG 1406 | C++11 | der Typ des neuen ersten Parameters, der für eine nicht-statische Member-Funktion-Template hinzugefügt wurde, war nicht relevant für den Ref-Qualifier dieses Templates |
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-Funktion hinzugefügt wurde, ohne Ref-Qualifier war ein Lvalue-Referenztyp Typ, selbst wenn diese Member-Funktion-Template mit einer Funktion-Template verglichen wird, deren erster Parameter den 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-Funktion-Templates in der partiellen Ordnung hinzugefügt |
nicht hinzugefügt |