Namespaces
Variants

Template argument deduction

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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 :

#include <iostream>
int main()
{
    std::cout << "Hello, world" << std::endl;
    // operator<< est recherché via ADL comme std::operator<<,
    // puis déduit en operator<<<char, std::char_traits<char>> les deux fois
    // std::endl est déduit en &std::endl<char, std::char_traits<char>>
}

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)

Table des matières

Déduction à partir d'un appel de fonction

La déduction d'arguments de template tente de déterminer les arguments de template (types pour les paramètres de template de type T i, templates pour les paramètres de template de template TT i, et valeurs pour les paramètres de template constants I i), qui peuvent être substitués dans chaque paramètre P pour produire le type déduit A , qui est le même que le type de l'argument A , après les ajustements listés ci-dessous.

S'il y a plusieurs paramètres, chaque paire P / A est déduite séparément et les arguments de modèle déduits sont ensuite combinés. Si la déduction échoue ou est ambiguë pour une paire P / A quelconque, ou si différentes paires produisent des arguments de modèle déduits différents, ou si un argument de modèle reste ni déduit ni explicitement spécifié, la compilation échoue.

Si la suppression des références et des qualificateurs cv de P donne std:: initializer_list < P '> et A est une liste d'initialisation entre accolades , alors la déduction est effectuée pour chaque élément de la liste d'initialisation, en prenant P' comme paramètre et l'élément de liste A' comme argument :

template<class T>
void f(std::initializer_list<T>);
f({1, 2, 3});  // P = std::initializer_list<T>, A = {1, 2, 3}
               // P'1 = T, A'1 = 1: déduit T = int
               // P'2 = T, A'2 = 2: déduit T = int
               // P'3 = T, A'3 = 3: déduit T = int
               // OK: déduit T = int
f({1, "abc"}); // P = std::initializer_list<T>, A = {1, "abc"}
               // P'1 = T, A'1 = 1: déduit T = int
               // P'2 = T, A'2 = "abc": déduit T = const char*
               // erreur: la déduction échoue, T est ambigu

Si la suppression des références et des qualificateurs cv de P donne P' [ N ], et A est une liste d'initialisation entre accolades non vide, alors la déduction est effectuée comme ci-dessus, sauf si N est un paramètre de template constant, il est déduit de la longueur de la liste d'initialisation :

template<class T, int N>
void h(T const(&)[N]);
h({1, 2, 3}); // déduit T = int, déduit N = 3
template<class T>
void j(T const(&)[3]);
j({42}); // déduit T = int, la borne du tableau n'est pas un paramètre, non considérée
struct Aggr
{
    int i;
    int j;
};
template<int N>
void k(Aggr const(&)[N]);
k({1, 2, 3});       // erreur: la déduction échoue, pas de conversion de int vers Aggr
k({{1}, {2}, {3}}); // OK: déduit N = 3
template<int M, int N>
void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // déduit M = 2, déduit N = 2
template<class T, int N>
void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // déduit T = Aggr, déduit N = 3

Si un pack de paramètres apparaît comme le dernier P , alors le type P est comparé au type A de chaque argument restant de l'appel. Chaque correspondance déduit les arguments de template pour la position suivante dans l'expansion du pack :

template<class... Types>
void f(Types&...);
void h(int x, float& y)
{
    const int z = x;
    f(x, y, z); // P = Types&..., A1 = x: déduit premier membre de Types... = int
                // P = Types&..., A2 = y: déduit deuxième membre de Types... = float
                // P = Types&..., A3 = z: déduit troisième membre de Types... = const int
                // appelle f<int, float, const int>
}


(depuis C++11)

Si P est un type de fonction, un type de pointeur vers fonction, ou un type de pointeur vers fonction membre et si A est un ensemble de fonctions surchargées ne contenant pas de modèles de fonction, la déduction d'argument de modèle est tentée avec chaque surcharge. Si une seule réussit, cette déduction réussie est utilisée. Si aucune ou plus d'une réussit, le paramètre de modèle est un contexte non déduit (voir ci-dessous) :

template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = ensemble de surcharge
      // P = T(*)(T), A1 = int(int): T déduit = int
      // P = T(*)(T), A2 = int(char): échec de déduction de T
      // une seule surcharge fonctionne, la déduction réussit

Avant que la déduction ne commence, les ajustements suivants à P et A sont effectués :

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 à partir de la conversion tableau-vers-pointeur ;
b) sinon, si A est un type de fonction, A est remplacé par le type pointeur obtenu à partir de la conversion fonction-vers-pointeur ;
c) sinon, si A est un type qualifié cv, les qualificatifs 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], ajusté en int* : T déduit = int*
void b(int);
f(b); // P = T, A = void(int), ajusté en void(*)(int) : T déduit = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, ajusté en int : T déduit = 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 à une valeur r vers un paramètre de modèle non qualifié cv (appelées références de transfert ), et que l'argument correspondant de l'appel de fonction est une lvalue, le type référence lvalue vers A est utilisé à la place de A pour la déduction (Note : ceci est la base du fonctionnement de std::forward . Note : dans la déduction d'arguments de modèle de classe , le 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 est une référence à valeur r vers T non qualifié cv (référence de transfert)
template<class T>
int g(const T&&); // P est une référence à valeur r vers T qualifié cv (pas spécial)
int main()
{
    int i;
    int n1 = f(i); // l'argument est une lvalue : appelle f<int&>(int&) (cas spécial)
    int n2 = f(0); // l'argument n'est pas une lvalue : appelle f<int>(int&&)
//  int n3 = g(i); // erreur : déduit g<int>(const int&&), qui
                   // ne peut pas lier une référence à valeur r à une lvalue
}

Après ces transformations, le processus de déduction se déroule comme décrit ci-dessous (cf. section déduction à partir d'un type ) et tente de trouver des arguments de template tels qu'ils rendraient le A déduit (c'est-à-dire, P après les ajustements listés ci-dessus et la substitution des paramètres de template déduits) identique au transformé A , c'est-à-dire A après les ajustements listés ci-dessus.

Si la déduction habituelle à partir de P et A échoue, les alternatives suivantes sont également considérées :

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 de pointeur ou de pointeur vers membre qui peut être converti vers 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*:
      // T déduit = int, A déduit = const int*
      // la conversion de qualification s'applique (de int* vers const int*)
3) Si P est une classe et P a la forme simple-template-id , 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 , 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>&, ajusté à P = B<T> (un simple-template-id), A = D<int>:
          // T déduit = int, A déduit = B<int>
          // A est dérivé de A déduit
}

Contextes non déduits

Dans les cas suivants, les types, modèles et constantes utilisés pour composer P ne participent pas à la déduction des arguments de modèle, mais utilisent plutôt les arguments de modèle qui ont été soit déduits ailleurs, soit explicitement spécifiés. Si un paramètre de modèle est utilisé uniquement dans des contextes non déduits et n'est pas explicitement spécifié, la déduction des arguments de modèle échoue.

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>
2) Un spécificateur d'indexation de paquet ou une expression d'indexation de paquet :
template<typename... Ts>
void f(Ts...[0], std::tuple<Ts...>);
f(3, std::tuple(5, 'A'));
// P2 = std::tuple<Ts...>, A2 = std::tuple<int, char>
// P2/A2: deduced first member of Ts... = int
// P2/A2: deduced second member of Ts... = char
// P1 = Ts...[0], A1 = int: Ts...[0] is in non-deduced context
(depuis C++26)
3) L'expression d'un spécificateur decltype :
template<typename T>
void f(decltype(*std::declval<T>()) arg);
int n;
f<int*>(n); // P = decltype(*declval<T>()), A = int: T is in non-deduced context
(depuis C++11)
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 :
template<typename T>
void out(const T& value) { std::cout << value; }
out("123");     // P = const T&, A = const char[4] lvalue: deduced T = char[4]
out(std::endl); // P = const T&, A = function template: T is in non-deduced context
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
8) Le paramètre P qui est un pack de paramètres et qui ne se trouve pas à la fin de la liste de paramètres :
template<class... Ts, class T>
void f1(T n, Ts... args);
template<class... Ts, class T>
void f2(Ts... args, T n);
f1(1, 2, 3, 4); // P1 = T, A1 = 1: déduit T = int
                // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: déduit Ts = [int, int, int]
f2(1, 2, 3, 4); // P1 = Ts...: Ts est un contexte non déduit
9) La liste de paramètres de template qui apparaît dans le paramètre P , et qui inclut une expansion de pack qui n'est pas à la toute fin de la liste de paramètres de template :
template<int...>
struct T {};
template<int... Ts1, int N, int... Ts2>
void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&);
template<int... Ts1, int N, int... Ts2>
void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&);
T<1, 2> t1;
T<1, -1, 0> t2;
good(t1, t2); // P1 = const T<N, Ts1...>&, A1 = T<1, 2>:
              // déduit N = 1, déduit Ts1 = [2]
              // P2 = const T<N, Ts2...>&, A2 = T<1, -1, 0>:
              // déduit N = 1, déduit Ts2 = [-1, 0]
bad(t1, t2);  // P1 = const T<Ts1..., N>&, A1 = T<1, 2>:
              // <Ts1..., N> est un contexte non déduit
              // P2 = const T<Ts2..., N>&, A2 = T<1, -1, 0>:
              // <Ts2..., N> est un contexte non déduit
(depuis C++11)
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 > , T est non déduit en raison de la règle n°1 (spécificateur de nom imbriqué), et T2 est non déduit car il fait partie du même nom de type, mais dans void ( * f ) ( typename A < T > :: B , A < T > ) , le T dans A < T > :: B est non déduit (en raison de la même règle), tandis que le T dans A < T > est déduit.

Déduction à partir d'un type

Étant donné un paramètre de fonction P qui dépend d'un ou plusieurs paramètres de template de type T i, de paramètres de template de template TT i, ou de paramètres de template constants I i, et l'argument correspondant A , la déduction a lieu si P a l'une des formes suivantes :

  • cv (optionnel) T ;
  • T* ;
  • T& ;
  • T&& ;
(depuis C++11)
  • T (optionnel) [ I (optionnel) ] ;
  • T (optionnel) ( U (optionnel) ) ;
(jusqu'en C++17)
  • T (optionnel) ( U (optionnel) ) noexcept( I (optionnel) ) ;
(depuis C++17)
  • T (optionnel) U (optionnel) ::* ;
  • TT (optionnel) <T> ;
  • TT (optionnel) <I> ;
  • TT (optionnel) <TU> ;
  • TT (optionnel) <> .

Dans les formulaires ci-dessus,

  • T (optionnel) ou U (optionnel) représente un type ou parameter-type-list qui satisfait soit ces règles récursivement, soit est un contexte non déduit dans P ou A , soit est le même type non dépendant dans P et A .
  • TT (optionnel) ou TU (optionnel) représente soit un modèle de classe soit un paramètre de modèle de modèle.
  • I (optionnel) représente une expression qui soit est un I , soit dépend des valeurs dans P ou A , soit a la même valeur constante dans P et A .
  • noexcept( I (optionnel) ) représente une spécification d'exception dans laquelle l'opérande potentiellement implicite du spécificateur noexcept satisfait les règles pour un I (optionnel) ci-dessus.
(depuis C++17)

Si P a l'une des formes qui inclut une liste de paramètres template <T> ou <I> , alors chaque élément P i de cette liste d'arguments template est comparé à l'argument template correspondant A i de son A . Si le dernier P i est une expansion de pack, alors son motif est comparé à chaque argument restant dans la liste d'arguments template de A . Un parameter pack final qui n'est pas autrement déduit est déduit comme un parameter pack vide.

Si P a l'une des formes qui incluent une liste de paramètres de fonction (T) , alors chaque paramètre P i de cette liste est comparé avec l'argument correspondant A i de la liste de paramètres de fonction de A . Si le dernier P i est une expansion de paquet, alors son déclarateur est comparé avec chaque A i restant dans la liste de types de paramètres de A .

Les formulaires peuvent être imbriqués et traités de manière récursive :

  • X < int > ( * ) ( char [ 6 ] ) est un exemple de T* , où T est X < int > ( char [ 6 ] ) ;
  • X < int > ( char [ 6 ] ) est un exemple de T (optionnel) ( U (optionnel) ) , où T est X < int > et U est char [ 6 ] ;
(jusqu'en C++17)
  • X < int > ( char [ 6 ] ) est un exemple de T (optionnel) ( U (optionnel) ) noexcept( I (optionnel) ) , où T est X < int > , U est char [ 6 ] , et I dans le spécificateur noexcept implicite est false ;
(depuis C++17)
  • X < int > est un exemple de TT (optionnel) <T> , où TT est X et T est int , et
  • char [ 6 ] est un exemple de T (optionnel) [ I (optionnel) ] , où T est char et I est std:: size_t ( 6 ) .

L'argument de type template ne peut pas être déduit du type d'un argument template constant :

template<typename T, T i>
void f(double a[10][i]);
double v[10][20];
f(v); // P = double[10][i], A = double[10][20]:
      // i peut être déduit comme égal à 20
      // mais T ne peut pas être déduit du type de i
(jusqu'en C++17)

Lorsque la valeur de l'argument correspondant à un paramètre template constant P qui est déclaré avec un type dépendant est déduite d'une expression, les paramètres template dans le type de P sont déduits du type de la valeur.

template<long n>
struct A {};
template<class T>
struct C;
template<class T, T n>
struct C<A<n>> { using Q = T; };
typedef long R;
typedef C<A<2>>::Q R; // OK : T a été déduit comme long
                      // à partir de la valeur d'argument template dans le type A<2>
template<auto X>
class bar {};
template<class T, T n>
void f(bar<n> x);
f(bar<3>{}); // OK : T a été déduit comme int (et n comme 3)
             // à partir de la valeur d'argument template dans le type bar<3>

Le type de N dans le type T[N] est std::size_t .

template<class T, T i>
void f(int (&a)[i]);
int v[10];
f(v); // OK : T est std::size_t

Le type de B dans noexcept ( B ) spécificateur d'un type de fonction est bool .

template<bool>
struct A {};
template<auto>
struct B;
template<auto X, void (*F)() noexcept(X)>
struct B<F> { A<X> ax; };
void f_nothrow() noexcept;
B<f_nothrow> bn; // OK : X est déduit comme true et le type de X est déduit comme bool.
(depuis C++17)

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 contextes

En 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 :

Déduction automatique de type

La déduction d'argument de template est utilisée dans les déclarations de variables, lors de la déduction de la signification du spécificateur auto à partir de l'initialiseur de la variable.

Le paramètre P est obtenu comme suit : dans T , le type déclaré de la variable qui inclut auto , chaque occurrence de auto est remplacée par un paramètre de template de type imaginaire U ou, si l'initialisation est une initialisation de liste par copie, par std::initializer_list<U> . L'argument A est l'expression d'initialisation. Après déduction de U à partir de P et A suivant les règles décrites ci-dessus, le U déduit est substitué dans P pour obtenir le type réel de la variable :

const auto& x = 1 + 2; // P = const U&, A = 1 + 2:
                       // mêmes règles que pour l'appel f(1 + 2) où f est
                       // template<class U> void f(const U& u)
                       // U déduit = int, le type de x est const int&
auto l = {13}; // P = std::initializer_list<U>, A = {13}:
               // U déduit = int, le type de l est std::initializer_list<int>

Dans l'initialisation directe par liste (mais pas dans l'initialisation de liste par copie), lors de la déduction de la signification du auto à partir d'une liste d'initialisation entre accolades, la liste d'initialisation entre accolades ne doit contenir qu'un seul élément, et le type de auto sera le type de cet élément :

auto x1 = {3}; // x1 est std::initializer_list<int>
auto x2{1, 2}; // erreur : pas un seul élément
auto x3{3};    // x3 est int
               // (avant N3922 x2 et x3 étaient tous deux std::initializer_list<int>)
(depuis C++11)

Fonctions à retour auto

La déduction d'argument de template est utilisée dans les déclarations de fonctions , lors de la déduction de la signification du spécificateur auto dans le type de retour de la fonction, à partir de l'instruction return.

Pour les fonctions à retour auto, le paramètre P est obtenu comme suit : dans T , le type de retour déclaré de la fonction qui inclut auto , chaque occurrence de auto est remplacée par un paramètre de template imaginaire U . L'argument A est l'expression de l'instruction return , et si l'instruction return n'a pas d'opérande, A est void ( ) . Après déduction de U à partir de P et A suivant les règles décrites ci-dessus, le U déduit est substitué dans T pour obtenir le type de retour réel :

auto f() { return 42; } // P = auto, A = 42:
                        // U déduit = int, le type de retour de f est int

Si une telle fonction a plusieurs instructions return, la déduction est effectuée pour chaque instruction return. Tous les types résultants doivent être identiques et deviennent le type de retour réel.

Si une telle fonction n'a pas d'instruction return, A est void ( ) lors de la déduction.

Remarque : la signification du spécificateur decltype ( auto ) dans les déclarations de variables et de fonctions n'utilise pas la déduction d'argument de template.

(depuis C++14)

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. P et A sont les mêmes que dans un appel de fonction régulier :

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 surcharge

La 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 P . Le type cible est le type de A :

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 P i et A i, si un P i est une référence à valeur droite vers un paramètre de template non qualifié cv (une "forwarding reference") et le A i correspondant est une référence à valeur gauche, alors P i est ajusté au type du paramètre de template (T&& devient T).

Si le type de retour du modèle de fonction est un espace réservé ( auto ou decltype ( auto ) ), ce type de retour est un contexte non déduit et est déterminé à partir de l'instanciation.

(depuis C++14)

Ordonnancement partiel

La 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 conversion

La 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 .

A est le type requis comme résultat de la conversion. P est le type de retour du modèle de fonction de conversion. Si P est un type référence, alors le type référencé est utilisé à la place de P pour les parties suivantes de la section.

Si A n'est pas un type référence :

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 A est qualifié cv, les qualificatifs cv de plus haut niveau sont ignorés. Si A est un type référence, le type référencé est utilisé pour la déduction.

Si la déduction habituelle à partir de P et A (comme décrit ci-dessus) échoue, les alternatives suivantes sont également considérées :

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*)
c) si A est un type de pointeur de fonction, le A déduit peut être un pointeur vers une fonction noexcept, convertible en A par conversion de pointeur de fonction ;
d) si A est un pointeur vers une fonction membre, le A déduit peut être un pointeur vers une fonction membre noexcept, convertible en A par conversion de pointeur de fonction.
(depuis C++17)

Voir member template pour les autres règles concernant les modèles de fonction de conversion.

Instanciation explicite

La 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.

P est le type du modèle de fonction qui est considéré comme une correspondance potentielle, et A est le type de fonction provenant de la déclaration. S'il n'y a aucune correspondance ou plus d'une correspondance (après classement partiel), la déclaration de fonction est mal formée :

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 P i et A i, si un P i est une référence à valeur droite vers un paramètre de template non qualifié cv (une "forwarding reference") et le A i correspondant est une référence à valeur gauche, alors P i est ajusté au type du paramètre de template (T&& devient T).

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 operator new .

P est le type du modèle de fonction qui est considéré comme une correspondance potentielle, et A est le type de fonction de la fonction de désallocation qui serait la correspondance pour l'opérateur new de placement considéré. S'il n'y a pas de correspondance ou plus d'une correspondance (après résolution de surcharge), la fonction de désallocation de placement n'est pas appelée (une fuite de mémoire peut se produire) :

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'alias

Les 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 P dont le type était non dépendant avant la substitution de tout argument de template explicitement spécifié, si l'argument correspondant A ne peut pas être implicitement converti en P , la déduction échoue.

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é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 Applicable à Comportement publié Comportement corrigé
CWG 70 C++98 la déduction des limites de tableau n'était pas spécifiée spécifiée comme non déduite
CWG 300 C++98 la déduction avait lieu pour les paramètres de fonction de forme
type(*)(T)/T(*)()/T(*)(T) , les pointeurs de fonction
correspondent à ces formes mais pas les références de fonction
modification de ces formes en
type(T)/T()/T(T) pour qu'elles
puissent également couvrir les références
CWG 322 C++98 les paramètres de type références n'étaient pas
ajustés pour utiliser le type référencé lors de la déduction
ajustement ajouté
CWG 976 C++98 dans la déduction pour les modèles d'opérateur de conversion,
const T& comme type de retour ne pouvait jamais correspondre
au type de résultat T
règles ajustées pour
permettre de telles correspondances
CWG 1387 C++11 l'expression d'un spécificateur decltype n'était pas un contexte non déduit elle l'est désormais
CWG 1391 C++98 l'effet des conversions implicites des arguments
non impliqués dans la déduction n'était pas spécifié
spécifié comme décrit ci-dessus
CWG 1591 C++11 impossible de déduire la limite de tableau et le type d'élément
à partir d'un braced-init-list
déduction autorisée
CWG 2052 C++98 déduire un opérateur avec des arguments non-classe
non-énumération était une erreur fatale
erreur non fatale s'il
existe d'autres surcharges
CWG 2091 C++98 la déduction d'un paramètre constant référence
ne fonctionnait pas à cause d'une incompatibilité de type avec l'argument
incompatibilité de type évitée
N3922 C++11 l'initialisation par liste directe de auto déduit std::initializer_list non valide pour plus d'un
élément, déduction du type
d'élément pour un seul élément
CWG 2355 C++17 la valeur dans un spécificateur noexcept d'un type de fonction n'était pas déductible rendue déductible