Template argument deduction
Afin d'instancier une function template , chaque argument de template doit être connu, mais il n'est pas nécessaire que chaque argument de template soit spécifié. Lorsque cela est possible, le compilateur déduira les arguments de template manquants à partir des arguments de la fonction. Cela se produit lors d'une tentative d'appel de fonction, lors de la prise d'adresse d'une function template, et dans certains autres contextes :
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) // et stocke son adresse dans ptr }
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 (qui peut impliquer une recherche dépendante des arguments ) et avant la substitution des arguments de template (qui peut impliquer SFINAE ) et la résolution de surcharge .
|
La déduction d'arguments de modèle est également effectuée lorsque le nom d'un modèle de classe est utilisé comme type d'un objet en cours de construction : std::pair p(2, 4.5); std::tuple t(4, 3, 2.5); std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); auto lck = std::lock_guard(foo.mtx); std::lock_guard lck2(foo.mtx, ul); La déduction d'arguments de modèle pour les modèles de classe a lieu dans les déclarations et dans les expressions de cast explicites ; voir déduction d'arguments de modèle de classe pour plus de détails. |
(depuis C++17) |
Contenu
Déduction à partir d'un appel de fonctionLa déduction d'arguments de modèle tente de déterminer les arguments de modèle (types pour les paramètres de type S'il y a plusieurs paramètres, chaque paire
Si template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = overload set
// P = T(*)(T), A1 = int(int): deduced T = int
// P = T(*)(T), A2 = int(char): fails to deduce T
// only one overload works, deduction succeeds
Avant le début de la déduction, les ajustements suivants à 1) Si
P n'est pas un type référence,a) si
A est un type tableau, A est remplacé par le type pointeur obtenu par conversion tableau en pointeur ;b) sinon, si
A est un type fonction, A est remplacé par le type pointeur obtenu par conversion fonction en pointeur ;c) sinon, si
A est un type qualifié cv, les qualificateurs cv de plus haut niveau sont ignorés pour la déduction :
template<class T>
void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
2) Si
P est un type qualifié cv, les qualificateurs cv de plus haut niveau sont ignorés pour la déduction.3) Si
P est un type référence, le type référencé est utilisé pour la déduction.4) Si
P est une référence rvalue à un paramètre de modèle non qualifié cv (appelée référence de transfert), et que l'argument d'appel de fonction correspondant est une lvalue, le type référence lvalue vers A est utilisé à la place de A pour la déduction (Note : c'est la base de l'action de std::forward. Note : dans la déduction d'arguments de modèle de classe, un paramètre de modèle d'un modèle de classe n'est jamais une référence de transfert(depuis C++17)):
template<class T>
int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
int main()
{
int i;
int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
// int n3 = g(i); // error: deduces to g<int>(const int&&), which
// cannot bind an rvalue reference to an lvalue
}
Après ces transformations, la déduction procède comme décrit ci-dessous (cf. section déduction à partir d'un type) et tente de trouver des arguments de modèle tels que le Si la déduction habituelle à partir de 1) Si
P est un type référence, le A déduit (c'est-à-dire le type référencé par la référence) peut être plus qualifié cv que le A transformé :
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
2) Le
A transformé peut être un autre type pointeur ou pointeur sur membre qui peut être converti en le A déduit via une conversion de qualification ou une conversion de pointeur de fonction(depuis C++17) :
template<typename T>
void f(const T*);
int* p;
f(p); // P = const T*, A = int*:
// deduced T = int, deduced A = const int*
// qualification conversion applies (from int* to const int*)
3) Si
P est une classe et que P a la forme simple-template-id (nom de modèle avec une liste d'arguments de modèle) ou spécificateur de couture avec une liste d'arguments de modèle(depuis C++26), alors le A transformé peut être une classe dérivée du A déduit. De même, si P est un pointeur vers une classe de la forme simple-template-id (nom de modèle avec une liste d'arguments de modèle) ou spécificateur de couture avec une liste d'arguments de modèle(depuis C++26), le A transformé peut être un pointeur vers une classe dérivée pointée par le A déduit :
template<class T>
struct B {};
template<class T>
struct D : public B<T> {};
template<class T>
void f(B<T>&) {}
void f()
{
D<int> d;
f(d); // P = B<T>&, adjusted to P = B<T> (a simple-template-id), A = D<int>:
// deduced T = int, deduced A = B<int>
// A is derived from deduced A
}
Contextes non déduits
Dans les cas suivants, les types, modèles et constantes utilisés pour composer
1)
Le
spécificateur de nom imbriqué
(tout ce qui se trouve à gauche de l'opérateur de résolution de portée
::
) d'un type spécifié à l'aide d'un
identifiant qualifié
:
// le modèle d'identité, souvent utilisé pour exclure des arguments spécifiques de la déduction // (disponible sous le nom std::type_identity à partir de C++20) template<typename T> struct identity { typedef T type; }; template<typename T> void bad(std::vector<T> x, T value = 1); template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1); std::vector<std::complex<double>> x; bad(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: T déduit = std::complex<double> // P2 = T, A2 = double // P2/A2: T déduit = double // erreur: la déduction échoue, T est ambigu good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: T déduit = std::complex<double> // P2 = identity<T>::type, A2 = double // P2/A2: utilise T déduit par P1/A1 car T est à gauche de :: dans P2 // OK: T = std::complex<double>
4)
Un argument de modèle constant ou une limite de tableau dans lequel une sous-expression fait référence à un paramètre de modèle :
template<std::size_t N> void f(std::array<int, 2 * N> a); std::array<int, 10> a; f(a); // P = std::array<int, 2 * N>, A = std::array<int, 10>: // 2 * N est un contexte non déduit, N ne peut pas être déduit // note : f(std::array<int, N> a) permettrait de déduire N
5)
Un paramètre de modèle utilisé dans le type de paramètre d'un paramètre de fonction qui a un argument par défaut qui est utilisé dans l'appel pour lequel la déduction d'argument est effectuée :
template<typename T, typename F> void f(const std::vector<T>& v, const F& comp = std::less<T>()); std::vector<std::string> v(3); f(v); // P1 = const std::vector<T>&, A1 = std::vector<std::string> lvalue // P1/A1 deduced T = std::string // P2 = const F&, A2 = std::less<std::string> rvalue // P2 is non-deduced context for F (template parameter) used in the // parameter type (const F&) of the function parameter comp, // that has a default argument that is being used in the call f(v)
6)
Le paramètre
P
, pour lequel
A
est une fonction ou un ensemble de surcharges tel que plus d'une fonction correspond à
P
ou aucune fonction ne correspond à
P
ou l'ensemble de surcharges inclut un ou plusieurs modèles de fonction :
7)
Le paramètre
P
, dont
A
est une liste d'initialisation entre accolades, mais
P
n'est pas
std::initializer_list
, une référence à celui-ci (éventuellement qualifiée cv), ou une référence à un tableau}} :
template<class T> void g1(std::vector<T>); template<class T> void g2(std::vector<T>, T x); g1({1, 2, 3}); // P = std::vector<T>, A = {1, 2, 3}: T est dans un contexte non déduit // erreur : T n'est pas explicitement spécifié ou déduit d'un autre P/A g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T est dans un contexte non déduit // P2 = T, A2 = int: T déduit = int
10)
Pour
P
de type tableau (mais pas référence à un tableau ou pointeur vers un tableau), la dimension principale du tableau :
template<int i> void f1(int a[10][i]); template<int i> void f2(int a[i][20]); // P = int[i][20], array type template<int i> void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array void g() { int a[10][20]; f1(a); // OK: déduit i = 20 f1<20>(a); // OK f2(a); // erreur: i est un contexte non déduit f2<10>(a); // OK f3(a); // OK: déduit i = 10 f3<10>(a); // OK }
Dans tous les cas, si une partie d'un nom de type est non déduite, l'ensemble du nom de type est un contexte non déduit. Cependant, les types composés peuvent inclure à la fois des noms de types déduits et non déduits. Par exemple, dans
A
<
T
>
::
B
<
T2
>
,
Déduction à partir d'un type
Étant donné un paramètre de fonction
Dans les formulaires ci-dessus,
Si
Si
Les formulaires peuvent être imbriqués et traités de manière récursive :
Si un paramètre de modèle constant d'un modèle de fonction est utilisé dans la liste des paramètres de modèle d'un paramètre de fonction (qui est également un modèle), et que l'argument de modèle correspondant est déduit, le type de l'argument de modèle déduit (tel que spécifié dans sa liste de paramètres de modèle englobante, ce qui signifie que les références sont préservées) doit correspondre exactement au type du paramètre de modèle constant, sauf que les qualificateurs cv sont supprimés, et sauf lorsque l'argument de modèle est déduit d'une limite de tableau—dans ce cas, tout type intégral est autorisé, même bool bien qu'il devienne toujours true : template<int i> class A {}; template<short s> void f(A<s>); // le type du paramètre de template constant est short void k1() { A<1> a; // le type du paramètre de template constant de a est int f(a); // P = A<(short)s>, A = A<(int)1> // erreur : l'argument de template constant déduit n'a pas le même // type que son argument de template correspondant f<1>(a); // OK : l'argument de template n'est pas déduit, // ceci appelle f<(short)1>(A<(short)1>) } template<int&> struct X; template<int& R> void k2(X<R>&); int n; void g(X<n> &x) { k2(x); // P = X<R>, A = X<n> // le type du paramètre est int& // le type de l'argument est int& dans la déclaration du template de struct X // OK (avec CWG 2091) : déduit que R fait référence à n } Le paramètre de modèle de type ne peut pas être déduit du type d'un argument par défaut de fonction : template<typename T> void f(T = 5, T = 7); void g() { f(1); // OK : appelle f<int>(1, 7) f(); // erreur : impossible de déduire T f<int>(); // OK : appelle f<int>(5, 7) } La déduction des paramètres template template peut utiliser le type utilisé dans la spécialisation de template utilisée dans l'appel de fonction : template<template<typename> class X> struct A {}; // A est un template avec un paramètre TT template<template<typename> class TT> void f(A<TT>) {} template<class T> struct B {}; A<B> ab; f(ab); // P = A<TT>, A = A<B>: TT déduit = B, appelle f(A<B>) Autres contextesEn plus des appels de fonction et des expressions d'opérateur, la déduction d'arguments de template est utilisée dans les situations suivantes :
Résolution de surcharge
La déduction d'argument de template est utilisée pendant
la résolution de surcharge
, lors de la génération de spécialisations à partir d'une fonction template candidate.
std::string s; std::getline(std::cin, s); // "std::getline" nomme 4 modèles de fonctions, // dont 2 sont des fonctions candidates (nombre correct de paramètres) // 1er modèle candidat : // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // la déduction détermine les paramètres de type CharT, Traits et Allocator // spécialisation std::getline<char, std::char_traits<char>, std::allocator<char>> // 2ème modèle candidat : // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // la déduction détermine les paramètres de type CharT, Traits et Allocator // spécialisation std::getline<char, std::char_traits<char>, std::allocator<char>> // la résolution de surcharge classe la liaison de référence à partir de la lvalue std::cin // et sélectionne la première des deux spécialisations candidates Si la déduction échoue, ou si la déduction réussit, mais que la spécialisation qu'elle produit serait invalide (par exemple, un opérateur surchargé dont les paramètres ne sont ni des types classe ni des types énumération), la spécialisation n'est pas incluse dans l'ensemble des surcharges, similaire à SFINAE . Adresse d'un ensemble de surchargeLa déduction d'argument de template est utilisée lors de la prise d'une adresse d'un ensemble surchargé , qui inclut les function templates.
Le type de fonction du modèle de fonction est
std::cout << std::endl; // std::endl désigne un modèle de fonction // type de endl P = // std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // paramètre A de operator<< = // std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (les autres surcharges de operator<< ne sont pas viables) // la déduction détermine les paramètres de type template CharT et Traits
Une règle supplémentaire est appliquée à la déduction dans ce cas : lors de la comparaison des paramètres de fonction
Ordonnancement partielLa déduction des arguments de template est utilisée lors de la mise en ordre partielle des surcharges de templates de fonction .
Modèle de fonction de conversionLa déduction d'argument de template est utilisée lors de la sélection des arguments de template de fonction de conversion définie par l'utilisateur .
Si
a)
si
P
est un type tableau, alors le type pointeur obtenu par conversion tableau-vers-pointeur est utilisé à la place de
P
;
b)
si
P
est un type de fonction, alors le type de pointeur de fonction obtenu par conversion fonction-vers-pointeur est utilisé à la place de
P
;
c)
si
P
est qualifié cv, les qualificateurs cv de plus haut niveau sont ignorés.
Si
Si la déduction habituelle à partir de
a)
si
A
est un type référence,
A
peut être plus qualifié cv que le
A
déduit ;
b)
si
A
est un type pointeur ou pointeur vers membre, le
A
déduit est autorisé à être n'importe quel pointeur convertible en
A
par conversion de qualification :
struct C { template<class T> operator T***(); }; C c; const int* const* const* p1 = c; // P = T***, A = const int* const* const* // déduction d'appel de fonction régulière pour // template<class T> void f(T*** p) comme si appelée avec l'argument // de type const int* const* const* échoue // déduction supplémentaire pour les fonctions de conversion détermine T = int // (A déduit est int***, convertible en const int* const* const*)
Voir member template pour les autres règles concernant les modèles de fonction de conversion. Instanciation expliciteLa déduction d'arguments de template est utilisée dans les instanciations explicites , les spécialisations explicites , et ces déclarations friend où le declarator-id fait référence à une spécialisation d'un template de fonction (par exemple, friend ostream & operator << <> ( ... ) ), si tous les arguments de template ne sont pas explicitement spécifiés ou par défaut, la déduction d'arguments de template est utilisée pour déterminer à quelle spécialisation de template il est fait référence.
template<class X> void f(X a); // 1er template f template<class X> void f(X* a); // 2ème template f template<> void f<>(int* a) {} // spécialisation explicite de f // P1 = void(X), A1 = void(int*): X déduit = int*, f<int*>(int*) // P2 = void(X*), A2 = void(int*): X déduit = int, f<int>(int*) // f<int*>(int*) et f<int>(int*) sont ensuite soumis à l'ordre partiel // qui sélectionne f<int>(int*) comme le template le plus spécialisé
Une règle supplémentaire s'applique à la déduction dans ce cas : lors de la comparaison des paramètres de fonction
Modèle de fonction de désallocation
La déduction d'argument de template est utilisée pour déterminer si une
fonction de désallocation
spécialisation de template correspond à une forme placement donnée de
struct X { X() { throw std::runtime_error(""); } static void* operator new(std::size_t sz, bool b) { return ::operator new(sz); } static void* operator new(std::size_t sz, double f) { return ::operator new(sz); } template<typename T> static void operator delete(void* ptr, T arg) { ::operator delete(ptr); } }; int main() { try { X* p1 = new (true) X; // lorsque X() lance une exception, operator delete est recherché // P1 = void(void*, T), A1 = void(void*, bool) : // T déduit = bool // P2 = void(void*, T), A2 = void(void*, double) : // T déduit = double // la résolution de surcharge sélectionne operator delete<bool> } catch(const std::exception&) {} try { X* p1 = new (13.2) X; // même recherche, sélectionne operator delete<double> } catch(const std::exception&) {} } Modèles d'aliasLes modèles d'alias ne sont pas déduits , sauf dans la déduction d'arguments de modèle de classe (depuis C++20) : template<class T> struct Alloc {}; template<class T> using Vec = vector<T, Alloc<T>>; Vec<int> v; template<template<class, class> class TT> void g(TT<int, Alloc<int>>); g(v); // OK : déduit TT = vector template<template<class> class TT> void f(TT<int>); f(v); // erreur : TT ne peut pas être déduit comme "Vec" car Vec est un alias template Conversions implicites
La déduction de type ne prend pas en compte les conversions implicites (autres que les ajustements de type listés ci-dessus) : c'est le rôle de la
résolution de surcharge
, qui intervient ultérieurement. Cependant, si la déduction réussit pour tous les paramètres participant à la déduction d'arguments de template, et que tous les arguments de template non déduits sont explicitement spécifiés ou par défaut, alors les paramètres de fonction restants sont comparés avec les arguments de fonction correspondants. Pour chaque paramètre restant
Les paramètres avec des types dépendants dans lesquels aucun paramètre de template ne participe à la déduction d'arguments de template, et les paramètres devenus non-dépendants en raison de la substitution d'arguments de template explicitement spécifiés seront vérifiés lors de la résolution de surcharge : template<class T> struct Z { typedef typename T::x xx; }; template<class T> typename Z<T>::xx f(void*, T); // #1 template<class T> void f(int, T); // #2 struct A {} a; int main() { f(1, a); // pour #1, la déduction détermine T = struct A, mais l'argument restant 1 // ne peut pas être implicitement converti en son paramètre void* : la déduction échoue // l'instanciation du type de retour n'est pas demandée // pour #2, la déduction détermine T = struct A, et l'argument restant 1 // peut être implicitement converti en son paramètre int : la déduction réussit // l'appel de fonction compile comme un appel à #2 (l'échec de déduction est SFINAE) } Rapports de défautsLes rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
|