Function template
Un modèle de fonction définit une famille de fonctions.
Syntaxe
template
<
liste-de-paramètres
>
déclaration-de-fonction
|
(1) | ||||||||
template
<
liste-de-paramètres
>
requires
contrainte
déclaration-de-fonction
|
(2) | (depuis C++20) | |||||||
| déclaration-de-fonction-avec-espaces-réservés | (3) | (depuis C++20) | |||||||
export
template
<
liste-de-paramètres
>
déclaration-de-fonction
|
(4) | (supprimé dans C++11) | |||||||
Explication
| parameter-list | - | une liste non vide séparée par des virgules des paramètres de template , chacun étant soit un paramètre constant , un paramètre de type , un paramètre de template , ou un pack de paramètres de l'un de ceux-ci (depuis C++11) . Comme pour tout template, les paramètres peuvent être contraints (depuis C++20) |
| function-declaration | - | une déclaration de fonction . Le nom de fonction déclaré devient un nom de template. |
| constraint | - | une expression de contrainte qui restreint les paramètres de template acceptés par ce template de fonction |
|
function-declaration-
with-placeholders |
- | une déclaration de fonction où le type d'au moins un paramètre utilise le placeholder auto ou Concept auto : la liste des paramètres de template aura un paramètre inventé pour chaque placeholder (voir Templates de fonction abrégés ci-dessous) |
|
|
(jusqu'à C++11) |
Modèle de fonction abrégéLorsque des types substituts (soit auto ou Concept auto ) apparaissent dans la liste de paramètres d'une déclaration de fonction ou d'une déclaration de modèle de fonction, la déclaration définit un modèle de fonction, et un paramètre de modèle inventé pour chaque substitut est ajouté à la liste des paramètres du modèle : 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); Les modèles de fonctions abrégés peuvent être spécialisés comme tous les modèles de fonctions. template<> void f4<int>(const int*, const double&); // specialization of f4<int, const double>
|
(depuis C++20) |
Signature de modèle de fonction
Chaque fonction template possède une signature.
La signature d'une tête de template est la liste de paramètres template , excluant les noms des paramètres template et les arguments par défaut , et la clause requires (si présente) (depuis C++20) .
La signature d'un patron de fonction contient le nom, la liste des types de paramètres, le type de retour , la clause requires finale (si présente) (depuis C++20) , et la signature du template-head . Sauf dans les cas suivants, sa signature contient également l'espace de noms englobant.
Si le modèle de fonction est un membre de classe, sa signature contient la classe dont la fonction est membre au lieu de l'espace de noms englobant. Sa signature contient également la clause requires finale (si présente) (depuis C++20) , le qualificatif de référence (si présent), et (depuis C++11) cv -qualifiers (si présents).
|
Si le modèle de fonction est un friend avec une contrainte impliquant les paramètres du modèle englobant, sa signature contient la classe englobante au lieu de l'espace de noms englobant. |
(since C++20) |
Instanciation de modèle de fonction
Un modèle de fonction en lui-même n'est pas un type, ni une fonction. Aucun code n'est généré à partir d'un fichier source qui ne contient que des définitions de modèles. Pour que du code apparaisse, un modèle doit être instancié : les arguments du modèle doivent être déterminés afin que le compilateur puisse générer une fonction réelle (ou une classe, à partir d'un modèle de classe).
Instanciation explicite
template
type-retour
nom
<
liste-arguments
>
(
liste-paramètres
)
;
|
(1) | ||||||||
template
type-retour
nom
(
liste-paramètres
)
;
|
(2) | ||||||||
extern
template
type-retour
nom
<
liste-arguments
>
(
liste-paramètres
)
;
|
(3) | (depuis C++11) | |||||||
extern
template
type-retour
nom
(
liste-paramètres
)
;
|
(4) | (depuis C++11) | |||||||
Une définition d'instanciation explicite force l'instanciation de la fonction ou de la fonction membre à laquelle elle se réfère. Elle peut apparaître n'importe où dans le programme après la définition du template, et pour une liste d'arguments donnée, n'est autorisée à apparaître qu'une seule fois dans le programme, aucun diagnostic requis.
|
Une déclaration d'instanciation explicite (un extern template) empêche les instanciations implicites : le code qui causerait autrement une instanciation implicite doit utiliser la définition d'instanciation explicite fournie ailleurs dans le programme. |
(depuis C++11) |
Un argument de modèle final peut être omis dans une instanciation explicite d'une spécialisation de modèle de fonction ou d'une spécialisation de modèle de fonction membre s'il peut être déduit du paramètre de fonction :
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // instancie f<double>(double) template void f<>(char); // instancie f<char>(char), argument template déduit template void f(int); // instancie f<int>(int), argument template déduit
L'instanciation explicite d'un modèle de fonction ou d'une fonction membre d'un modèle de classe ne peut pas utiliser
inline
ou
constexpr
. Si la déclaration de l'instanciation explicite nomme une fonction membre spéciale implicitement déclarée, le programme est mal formé.
L'instanciation explicite d'un constructeur ne peut pas utiliser une liste de paramètres de template (syntaxe (1) ), ce qui n'est jamais nécessaire car ils peuvent être déduits (syntaxe (2) ).
|
L'instanciation explicite d'un destructeur potentiel doit nommer le destructeur sélectionné de la classe. |
(depuis C++20) |
Les déclarations d'instanciation explicite ne suppriment pas l'instanciation implicite des inline functions, auto -declarations, références, et spécialisations de modèles de classe. (ainsi, lorsque la fonction inline qui fait l'objet d'une déclaration d'instanciation explicite est ODR-utilisée, elle est implicitement instanciée pour l'inlining, mais sa copie hors-ligne n'est pas générée dans cette unité de traduction)
La définition d'instanciation explicite d'un modèle de fonction avec arguments par défaut n'est pas une utilisation des arguments, et ne tente pas de les initialiser :
char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // Correct même si &p n'est pas un int.
Instanciation implicite
Lorsque le code fait référence à une fonction dans un contexte qui nécessite que la définition de la fonction existe , ou si l'existence de la définition affecte la sémantique du programme (depuis C++11) , et que cette fonction particulière n'a pas été explicitement instanciée, une instanciation implicite se produit. La liste des arguments template ne doit pas être fournie si elle peut être déduite du contexte.
#include <iostream> template<typename T> void f(T s) { std::cout << s << '\n'; } int main() { f<double>(1); // instancie et appelle f<double>(double) f<>('a'); // instancie et appelle f<char>(char) f(7); // instancie et appelle f<int>(int) void (*pf)(std::string) = f; // instancie f<string>(string) pf("∇"); // appelle f<string>(string) }
|
L'existence d'une définition de fonction est considérée comme affectant la sémantique du programme si la fonction est nécessaire pour l'évaluation constante par une expression, même si l'évaluation constante de l'expression n'est pas requise ou si l'évaluation d'expression constante n'utilise pas la définition. 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 n'est pas potentiellement évalué de manière constante h<false, int>(0); // erreur: instancie f<int> même si B évalue à false // et l'initialisation de liste de int à partir de int ne peut pas être restrictive } |
(depuis C++11) |
Note : omettre
<>
entièrement permet à la
résolution de surcharge
d'examiner à la fois les surcharges de modèle et non-modèle.
Déduction d'arguments de template
Afin d'instancier un modèle de fonction, chaque argument de modèle doit être connu, mais il n'est pas nécessaire que chaque argument de modèle soit spécifié. Lorsque cela est possible, le compilateur déduira les arguments de modèle manquants à partir des arguments de la fonction. Cela se produit lorsqu'un appel de fonction est tenté et lorsqu'une adresse d'un modèle de fonction est prise.
template<typename To, typename From> To convert(From f); void g(double d) { int i = convert<int>(d); // appelle convert<int,double>(double) char c = convert<char>(d); // appelle convert<char,double>(double) int(*ptr)(float) = convert; // instancie convert<int, float>(float) }
Ce mécanisme permet d'utiliser des opérateurs template, puisqu'il n'existe pas de syntaxe pour spécifier des arguments template pour un opérateur autrement qu'en le réécrivant comme une expression d'appel de fonction.
La déduction des arguments de template a lieu après la recherche de nom du template de fonction (ce qui peut impliquer une recherche dépendante des arguments ) et avant la résolution de surcharge .
Voir la déduction d'arguments de template pour plus de détails.
Arguments de modèle explicites
Les arguments template d'un fonction template peuvent être obtenus à partir de
- déduction d'argument de modèle
- arguments de modèle par défaut
- spécifiés explicitement, ce qui peut être fait dans les contextes suivants :
-
- dans une expression d'appel de fonction
- lors de la prise d'adresse d'une fonction
- lors de l'initialisation d'une référence à une fonction
- lors de la formation d'un pointeur vers une fonction membre
- dans une spécialisation explicite
- dans une instanciation explicite
- dans une déclaration friend
Il n'existe aucun moyen de spécifier explicitement les arguments template aux opérateurs surchargés , fonctions de conversion , et constructeurs, car ils sont appelés sans utilisation du nom de fonction.
Les arguments de modèle spécifiés doivent correspondre aux paramètres du modèle en nature (c'est-à-dire type pour type, constante pour constante, et modèle pour modèle). Il ne peut pas y avoir plus d'arguments que de paramètres (sauf si un paramètre est un parameter pack, auquel cas il doit y avoir un argument pour chaque paramètre non-pack) (depuis C++11) .
Les arguments constants spécifiés doivent soit correspondre aux types des paramètres de modèle constants correspondants, soit être convertibles vers eux .
Les paramètres de fonction qui ne participent pas à la déduction d'arguments de modèle (par exemple, si les arguments de modèle correspondants sont explicitement spécifiés) sont soumis à des conversions implicites vers le type du paramètre de fonction correspondant (comme dans la résolution de surcharge habituelle).
|
Un paramètre pack de template explicitement spécifié peut être étendu par déduction d'arguments de template s'il y a des arguments supplémentaires : template<class... Types> void f(Types... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} } |
(depuis C++11) |
Substitution des arguments de template
Lorsque tous les arguments template ont été spécifiés, déduits ou obtenus à partir des arguments template par défaut, chaque utilisation d'un paramètre template dans la liste des paramètres de la fonction est remplacée par les arguments template correspondants.
L'échec de substitution (c'est-à-dire l'incapacité à remplacer les paramètres du modèle par les arguments de modèle déduits ou fournis) d'un modèle de fonction retire ce modèle de fonction de l' ensemble de surcharge . Cela permet plusieurs façons de manipuler les ensembles de surcharge en utilisant la métaprogrammation de modèles : voir SFINAE pour plus de détails.
Après substitution, tous les paramètres de fonction de type tableau et fonction sont ajustés en pointeurs et tous les qualificateurs cv de plus haut niveau sont supprimés des paramètres de fonction (comme dans une déclaration de fonction régulière).
La suppression des qualificatifs cv de plus haut niveau n'affecte pas le type du paramètre tel qu'il apparaît dans la fonction :
template<class T> void f(T t); template<class X> void g(const X x); template<class Z> void h(Z z, Z* zp); // deux fonctions différentes avec le même type, mais // dans la fonction, t a des qualifications cv différentes f<int>(1); // le type de fonction est void(int), t est int f<const int>(1); // le type de fonction est void(int), t est const int // deux fonctions différentes avec le même type et le même x // (les pointeurs vers ces deux fonctions ne sont pas égaux, // et les statiques locales auraient des adresses différentes) g<int>(1); // le type de fonction est void(int), x est const int g<const int>(1); // le type de fonction est void(int), x est const int // seules les qualifications cv de niveau supérieur sont supprimées : h<const int>(1, NULL); // le type de fonction est void(int, const int*) // z est const int, zp est const int*
Surcharge de modèle de fonction
Les modèles de fonction et les fonctions non modèles peuvent être surchargés.
Une fonction non template est toujours distincte d'une spécialisation de template avec le même type. Les spécialisations de différents templates de fonction sont toujours distinctes les unes des autres même si elles ont le même type. Deux templates de fonction avec le même type de retour et la même liste de paramètres sont distincts et peuvent être distingués par leur liste d'arguments template explicite.
Lorsqu'une expression utilisant des paramètres de template de type ou constants apparaît dans la liste des paramètres de la fonction ou dans le type de retour, cette expression reste une partie de la signature du template de fonction aux fins de surcharge :
template<int I, int J> A<I+J> f(A<I>, A<J>); // surcharge #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // identique à #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // surcharge #2
Deux expressions impliquant des paramètres de template sont dites équivalentes si deux définitions de fonctions contenant ces expressions seraient identiques selon la Règle de Définition Unique (ODR) , c'est-à-dire que les deux expressions contiennent la même séquence de jetons dont les noms sont résolus aux mêmes entités via la recherche de nom, sauf que les paramètres de template peuvent être nommés différemment. Deux expressions lambda ne sont jamais équivalentes. (depuis C++20)
template<int I, int J> void f(A<I+J>); // surcharge de template #1 template<int K, int L> void f(A<K+L>); // équivalent à #1
Lors de la détermination si deux expressions dépendantes sont équivalentes, seuls les noms dépendants impliqués sont considérés, et non les résultats de la recherche de nom. Si plusieurs déclarations du même template diffèrent dans le résultat de la recherche de nom, la première de ces déclarations est utilisée :
template<class T> decltype(g(T())) h(); // decltype(g(T())) est un type dépendant int g(int); template<class T> decltype(g(T())) h() { // la redéclaration de h() utilise la recherche antérieure return g(T()); // bien que la recherche ici trouve g(int) } int i = h<int>(); // la substitution d'argument template échoue ; g(int) // n'était pas dans la portée à la première déclaration de h()
Deux modèles de fonction sont considérés équivalents si
- ils sont déclarés dans la même portée
- ils ont le même nom
- ils ont des listes de paramètres de template équivalentes , ce qui signifie que les listes sont de même longueur, et pour chaque paire de paramètres correspondante, toutes les conditions suivantes sont vraies :
-
- les deux paramètres sont du même type (tous deux des types, tous deux des constantes, ou tous deux des modèles)
|
(depuis C++11) |
-
- si constantes, leurs types sont équivalents,
- si templates, leurs paramètres de template sont équivalents,
|
(depuis C++20) |
- les expressions impliquant des paramètres de template dans leurs types de retour et listes de paramètres sont équivalentes
|
(depuis C++20) |
Deux expressions potentiellement évaluées (depuis C++20) impliquant des paramètres de template sont dites fonctionnellement équivalentes si elles ne sont pas équivalentes , mais que pour tout ensemble donné d'arguments de template, l'évaluation des deux expressions produit la même valeur.
Deux modèles de fonction sont considérés fonctionnellement équivalents s'ils sont équivalents , sauf qu'une ou plusieurs expressions impliquant des paramètres de template dans leurs types de retour et listes de paramètres sont fonctionnellement équivalentes .
|
De plus, deux modèles de fonction sont fonctionnellement équivalents mais pas équivalents si leurs contraintes sont spécifiées différemment, mais qu'ils acceptent et sont satisfaits par le même ensemble de listes d'arguments de modèle. |
(since C++20) |
Si un programme contient des déclarations de modèles de fonction qui sont fonctionnellement équivalentes mais pas équivalentes , le programme est mal formé ; aucun diagnostic n'est requis.
// équivalent template<int I> void f(A<I>, A<I+10>); // surcharge #1 template<int I> void f(A<I>, A<I+10>); // redéclaration de la surcharge #1 // non équivalent template<int I> void f(A<I>, A<I+10>); // surcharge #1 template<int I> void f(A<I>, A<I+11>); // surcharge #2 // fonctionnellement équivalent mais non équivalent // Ce programme est mal formé, aucun diagnostic requis template<int I> void f(A<I>, A<I+10>); // surcharge #1 template<int I> void f(A<I>, A<I+1+2+3+4>); // fonctionnellement équivalent
Lorsque la même spécialisation de modèle de fonction correspond à plusieurs modèles de fonction surchargés (cela résulte souvent de la déduction d'arguments de modèle ), l'ordonnancement partiel des modèles de fonction surchargés est effectué pour sélectionner la meilleure correspondance.
Plus précisément, l'ordonnancement partiel intervient dans les situations suivantes :
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;
|
Cette section est incomplète
Raison : exemple minimal |
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
Informellement, « A est plus spécialisé que B » signifie « A accepte moins de types que B ».
Formellement, pour déterminer laquelle de deux modèles de fonction est la plus spécialisée, le processus d'ordonnancement partiel transforme d'abord l'un des deux modèles comme suit :
- Pour chaque type, constante et paramètre de modèle, incluant les parameter packs, (depuis C++11) un type fictif unique, une valeur ou un modèle est généré et substitué dans le type de fonction du modèle
-
Si un seul des deux modèles de fonction comparés est une fonction membre, et que ce modèle de fonction est un membre non statique d'une classe
A, un nouveau premier paramètre est inséré dans sa liste de paramètres. Étant donné cv comme les qualificateurs cv du modèle de fonction et ref comme le ref-qualifier du modèle de fonction (depuis C++11) , le nouveau type de paramètre est cvA&sauf si ref est&&, ou ref n'est pas présent et le premier paramètre de l'autre modèle a un type de référence rvalue, dans ce cas le type est cvA&&(depuis C++11) . Cela aide à l'ordonnancement des opérateurs, qui sont recherchés à la fois comme fonctions membres et non membres :
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; // la déduction d'argument de template pour int B<A>::operator*(R&) donne R=A // pour int operator*(T&, R&), T=B<A>, R=A // Pour l'ordre partiel, le template membre B<A>::operator* // est transformé en template<class R> int operator*(B<A>&, R&); // l'ordre partiel entre // int operator*( T&, R&) T=B<A>, R=A // et int operator*(B<A>&, R&) R=A // sélectionne int operator*(B<A>&, A&) comme plus spécialisé }
Après que l'un des deux modèles a été transformé comme décrit ci-dessus, la déduction d'arguments de modèle est exécutée en utilisant le modèle transformé comme modèle d'argument et le type de modèle original de l'autre modèle comme modèle de paramètre. Le processus est ensuite répété en utilisant le second modèle (après transformations) comme argument et le premier modèle dans sa forme originale comme paramètre.
Les types utilisés pour déterminer l'ordre dépendent du contexte :
- dans le contexte d'un appel de fonction, les types sont ceux des paramètres de fonction pour lesquels l'appel de fonction a des arguments (les arguments par défaut, les packs de paramètres, (depuis C++11) et les paramètres ellipse ne sont pas pris en compte -- voir les exemples ci-dessous)
- dans le contexte d'un appel à une fonction de conversion définie par l'utilisateur, les types de retour des modèles de fonction de conversion sont utilisés
- dans d'autres contextes, le type du modèle de fonction est utilisé
Chaque type de la liste ci-dessus du paramètre template est déduit. Avant que la déduction ne commence, chaque paramètre
P
du paramètre template et l'argument correspondant
A
du template d'argument sont ajustés comme suit :
-
Si les deux
PetAsont des types référence auparavant, déterminer lequel est le plus qualifié cv (dans tous les autres cas, les qualifications cv sont ignorées pour les besoins de l'ordre partiel) -
Si
Pest un type référence, il est remplacé par le type référencé -
Si
Aest un type référence, il est remplacé par le type référencé -
Si
Pest qualifié cv,Pest remplacé par sa version non qualifiée cv -
Si
Aest qualifié cv,Aest remplacé par sa version non qualifiée cv
Après ces ajustements, la déduction de
P
à partir de
A
est effectuée selon
la déduction d'argument de template à partir d'un type
.
|
Si
Si
|
(depuis C++11) |
Si l'argument
A
du template-1 transformé peut être utilisé pour déduire le paramètre correspondant
P
du template-2, mais pas l'inverse, alors cet
A
est plus spécialisé que
P
en ce qui concerne le(s) type(s) qui sont déduits par cette paire
P/A
.
Si la déduction réussit dans les deux directions, et que les types originaux
P
et
A
étaient des types référence, alors des tests supplémentaires sont effectués :
-
Si
Aétait une référence lvalue etPétait une référence rvalue,Aest considéré comme plus spécialisé queP -
Si
Aétait plus qualifié cv queP,Aest considéré comme plus spécialisé queP
Dans tous les autres cas, aucun des deux modèles n'est plus spécialisé que l'autre en ce qui concerne le(s) type(s) déduit(s) par cette
P/A
paire.
Après avoir examiné chaque
P
et
A
dans les deux directions, si, pour chaque type qui a été examiné,
- template-1 est au moins aussi spécialisé que template-2 pour tous les types
- template-1 est plus spécialisé que template-2 pour certains types
- template-2 n'est pas plus spécialisé que template-1 pour aucun type OU n'est pas au moins aussi spécialisé pour aucun type
Alors template-1 est plus spécialisé que template-2. Si les conditions ci-dessus sont vraies après avoir inversé l'ordre des templates, alors template-2 est plus spécialisé que template-1. Sinon, aucun template n'est plus spécialisé que l'autre.
|
En cas d'égalité, si un modèle de fonction a un paramètre pack de fin et que l'autre ne l'a pas, celui avec le paramètre omis est considéré comme plus spécialisé que celui avec le paramètre pack vide. |
(depuis C++11) |
Si, après avoir examiné toutes les paires de modèles surchargés, il en existe un qui est sans ambiguïté plus spécialisé que tous les autres, la spécialisation de ce modèle est sélectionnée, sinon la compilation échoue.
Dans les exemples suivants, les arguments fictifs seront appelés U1, U2 :
template<class T> void f(T); // modèle #1 template<class T> void f(T*); // modèle #2 template<class T> void f(const T*); // modèle #3 void m() { const int* p; f(p); // la résolution de surcharge choisit : #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // ordre partiel : // #1 depuis #2 transformé : void(T) depuis void(U1*): P=T A=U1*: déduction ok: T=U1* // #2 depuis #1 transformé : void(T*) depuis void(U1): P=T* A=U1: déduction échoue // #2 est plus spécialisé que #1 par rapport à T // #1 depuis #3 transformé : void(T) depuis void(const U1*): P=T, A=const U1*: ok // #3 depuis #1 transformé : void(const T*) depuis void(U1): P=const T*, A=U1: échoue // #3 est plus spécialisé que #1 par rapport à T // #2 depuis #3 transformé : void(T*) depuis void(const U1*): P=T* A=const U1*: ok // #3 depuis #2 transformé : void(const T*) depuis void(U1*): P=const T* A=U1*: échoue // #3 est plus spécialisé que #2 par rapport à T // résultat : #3 est sélectionné // en d'autres termes, f(const T*) est plus spécialisé que f(T) ou 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); // déduction pour #1 : void f(T, T*) [T = int] // déduction pour #2 : void f(T, int*) [T = int] // ordre partiel : // #1 depuis #2 : void(T,T*) depuis void(U1,int*) : P1=T, A1=U1 : T=U1 // P2=T*, A2=int* : T=int : échec // #2 depuis #1 : void(T,int*) depuis void(U1,U2*) : P1=T A1=U1 : T=U1 // P2=int* A2=U2* : échec // aucune n'est plus spécialisée par rapport à T, l'appel est ambigu }
template<class T> void g(T); // template #1 template<class T> void g(T&); // template #2 void m() { float x; g(x); // déduction depuis #1 : void g(T ) [T = float] // déduction depuis #2 : void g(T&) [T = float] // ordre partiel : // #1 depuis #2 : void(T) depuis void(U1&) : P=T, A=U1 (après ajustement), ok // #2 depuis #1 : void(T&) depuis void(U1) : P=T (après ajustement), A=U1 : ok // aucun n'est plus spécialisé par rapport à T, l'appel est ambigu }
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); // déduction depuis #1 : void h(const T &) [T = A<int>] // déduction depuis #2 : void h(A<T> &) [T = int] // ordre partiel : // #1 depuis #2 : void(const T&) depuis void(A<U1>&) : P=T A=A<U1> : ok T=A<U1> // #2 depuis #1 : void(A<T>&) depuis void(const U1&) : P=A<T> A=const U1 : échec // #2 est plus spécialisée que #1 par rapport à T const A<int> z2; h(z2); // déduction depuis #1 : void h(const T&) [T = A<int>] // déduction depuis #2 : void h(A<T>&) [T = int], mais la substitution échoue // une seule surcharge disponible, ordre partiel non testé, #1 est appelée }
Puisqu'un contexte d'appel ne considère que les paramètres pour lesquels il existe des arguments d'appel explicites, les packs de paramètres de fonction, (depuis C++11) les paramètres ellipses, et les paramètres avec arguments par défaut, pour lesquels il n'existe pas d'argument d'appel explicite, sont ignorés :
template<class T> void f(T); // #1 template<class T> void f(T*, int = 1); // #2 void m(int* ip) { int* ip; f(ip); // appelle #2 (T* est plus spécialisé que T) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // appelle #2 (T* est plus spécialisé que 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); // appelle #2 f<int>(42); // erreur : ambigu }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // erreur : ambigu }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // appelle #2 en raison du critère de départage entre le pack de paramètres et aucun paramètre // (note : était ambigu entre DR692 et 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 : appelle #1 (T* est plus spécialisé que T) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // OK : sélectionne #2 ; le template non variadique est plus spécialisé que // le template variadique (était ambigu avant DR1395 car la déduction // échouait dans les deux 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(); // appelle #1 f(1, 2, 3); // appelle #2 f(1, 2); // appelle #3; le template non variadique #3 est plus // spécialisé que les templates variadiques #1 et #2
Lors de la déduction des arguments de template dans le cadre du processus de classement partiel, les paramètres de template ne nécessitent pas d'être appariés avec les arguments, si l'argument n'est pas utilisé dans l'un des types considérés pour le classement partiel
template<class T> T f(int); // #1 template<class T, class U> T f(U); // #2 void g() { f<int>(1); // spécialisation de #1 est explicite : T f(int) [T = int] // spécialisation de #2 est déduite : T f(U) [T = int, U = int] // ordre partiel (en considérant uniquement le type d'argument) : // #1 depuis #2 : T(int) depuis U1(U2) : échoue // #2 depuis #1 : T(U) depuis U1(int) : ok : U=int, T non utilisé // appelle #1 }
|
Le classement partiel des modèles de fonction contenant des packs de paramètres de template est indépendant du nombre d'arguments déduits pour ces packs de paramètres de template. 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 |
(depuis C++11) |
|
Cette section est incomplète
Raison : 14.8.3[temp.over] |
Pour compiler un appel à une fonction template, le compilateur doit choisir entre les surcharges non-template, les surcharges template et les spécialisations des surcharges template.
template<class T> void f(T); // #1 : surcharge de template template<class T> void f(T*); // #2 : surcharge de template void f(double); // #3 : surcharge non-template template<> void f(int); // #4 : spécialisation de #1 f('a'); // appelle #1 f(new int(1)); // appelle #2 f(1.0); // appelle #3 f(1); // appelle #4
Surcharges de fonctions vs spécialisations de fonctions
Notez que seules les surcharges non-template et les templates primaires participent à la résolution de surcharge. Les spécialisations ne sont pas des surcharges et ne sont pas prises en compte. Ce n'est qu'après que la résolution de surcharge a sélectionné la fonction template primaire la plus adaptée que ses spécialisations sont examinées pour déterminer si l'une d'elles constitue un meilleur match.
template<class T> void f(T); // #1 : surcharge pour tous les types template<> void f(int*); // #2 : spécialisation de #1 pour les pointeurs vers int template<class T> void f(T*); // #3 : surcharge pour tous les types de pointeurs f(new int(1)); // appelle #3, même si la spécialisation de #1 serait une correspondance parfaite
Il est important de se rappeler cette règle lors de l'ordonnancement des fichiers d'en-tête d'une unité de traduction. Pour plus d'exemples sur l'interaction entre les surcharges de fonctions et les spécialisations de fonctions, développez ci-dessous :
| Exemples |
|---|
|
Considérez d'abord quelques scénarios où la recherche dépendante des arguments n'est pas employée. Pour cela, nous utilisons l'appel ( f ) ( t ) . Comme décrit dans ADL , l'encapsulation du nom de fonction entre parenthèses supprime la recherche dépendante des arguments.
Exécuter ce code
#include <iostream> struct A {}; template<class T> void f(T) { std::cout << "#1\n"; } // surcharge #1 avant f() POR template<class T> void f(T*) { std::cout << "#2\n"; } // surcharge #2 avant f() POR template<class T> void g(T* t) { (f)(t); // f() POR } int main() { A* p = nullptr; g(p); // POR de g() et f() } // #1 et #2 sont ajoutés à la liste des candidats ; // #2 est sélectionné car c'est une meilleure correspondance. Sortie : #2
Exécuter ce code
#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 de g() et f() } // Seule #1 est ajoutée à la liste des candidats ; #2 est définie après POR ; // par conséquent, elle n'est pas considérée pour la surcharge même si c'est une meilleure correspondance. Sortie : #1
Exécuter ce code
#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 de g() et f() } // #1 est ajouté à la liste des candidats ; #3 est une meilleure correspondance définie après POR. La // liste des candidats se compose de #1 qui est finalement sélectionné. Après cela, la spécialisation // explicite #3 de #1 déclarée après POI est sélectionnée car c'est une meilleure correspondance. // Ce comportement est régi par 14.7.3/6 [temp.expl.spec] et n'a rien à voir avec ADL. Sortie : #3
Exécuter ce code
#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 de g() et f() } // #1 est le seul membre de la liste des candidats et il est finalement sélectionné. // Après cela, la spécialisation explicite #3 est ignorée car elle spécialise // en réalité #2 déclarée après le POR. Sortie : #1
Exécuter ce code
#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 de g() et f() } // #1 est ajouté à la liste des candidats suite à la recherche ordinaire ; // #2 est défini après POR mais est ajouté à la liste des candidats via ADL lookup. // #2 est sélectionné car c'est la meilleure correspondance. Sortie : #2
Exécuter ce code
#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 de g() et f() } // #1 est ajouté à la liste des candidats suite à la recherche ordinaire ; // #2 est défini après POR mais est ajouté à la liste des candidats via ADL. // #2 est sélectionné parmi les templates primaires, étant la meilleure correspondance. // Puisque #3 est déclaré avant #2, c'est une spécialisation explicite de #1. // Par conséquent, la sélection finale est #2. Sortie : #2
Exécuter ce code
#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 de g() et f() } // #1 est ajouté à la liste des candidats suite à la recherche ordinaire ; // #2 est défini après POR mais est ajouté à la liste des candidats via ADL. // #2 est sélectionné parmi les templates primaires, étant la meilleure correspondance. // Puisque #3 est déclaré après #2, c'est une spécialisation explicite de #2 ; // par conséquent, sélectionné comme fonction à appeler. Sortie : #3
|
Pour les règles détaillées sur la résolution de surcharge, consultez overload resolution .
Spécialisation de modèle de fonction
|
Cette section est incomplète
Raison : 14.8[temp.fct.spec] (notez que 14.8.1[temp.arg.explicit] est déjà dans l'article sur la spécialisation complète : soit les spécificités des fonctions vont ici : manque de partielles, interaction avec les surcharges de fonctions, ou simplement se référer à cela |
Mots-clés
template , extern (depuis C++11)
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement tel que publié | Comportement correct |
|---|---|---|---|
| CWG 214 | C++98 | la procédure exacte de l'ordonnancement partiel n'était pas spécifiée | spécification ajoutée |
| CWG 532 | C++98 |
l'ordre entre un modèle de fonction membre non statique
et un modèle de fonction non membre n'était pas spécifié |
spécification ajoutée |
| CWG 581 | C++98 |
la liste d'arguments template dans une spécialisation explicite ou
une instanciation d'un modèle de constructeur était autorisée |
interdite |
| CWG 1321 | C++98 |
il n'était pas clair si les mêmes noms dépendants dans la
première déclaration et une redéclaration sont équivalents |
ils sont équivalents et
la signification est la même que dans la première déclaration |
| CWG 1395 | C++11 |
la déduction échouait quand A provenait d'un pack,
et il n'y avait pas de bris d'égalité pour les packs vides |
déduction autorisée,
bris d'égalité ajouté |
| CWG 1406 | C++11 |
le type du nouveau premier paramètre ajouté pour
un modèle de fonction membre non statique n'était pas pertinent par rapport au ref-qualifier de ce modèle |
le type est une référence rvalue
si le ref-qualifier est
&&
|
| CWG 1446 | C++11 |
le type du nouveau premier paramètre ajouté pour un modèle de fonction
membre non statique sans ref-qualifier était une référence lvalue, même si ce modèle de fonction membre est comparé à un modèle de fonction dont le premier paramètre a un type référence rvalue |
le type est une
référence rvalue dans ce cas |
| CWG 2373 | C++98 |
de nouveaux premiers paramètres étaient ajoutés aux listes de paramètres
des modèles de fonctions membres statiques dans l'ordonnancement partiel |
non ajoutés |