Class template argument deduction (CTAD) (since C++17)
Afin d'instancier un class template , chaque argument de template doit être connu, mais pas nécessairement spécifié. Dans les contextes suivants, le compilateur déduira les arguments de template à partir du type de l'initialiseur :
- toute déclaration qui spécifie l'initialisation d'une variable et d'un modèle de variable, dont le type déclaré est le modèle de classe (éventuellement qualifié cv ) :
std::pair p(2, 4.5); // déduit std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // identique à auto t = std::make_tuple(4, 3, 2.5); std::less l; // identique à std::less<void> l;
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // type alloué est A<int>
auto lck = std::lock_guard(mtx); // déduit std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // déduit std::back_insert_iterator<T>, // où T est le type du conteneur vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // déduit Foo<T>, // où T est le type unique du lambda
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(depuis C++20) |
Table des matières |
Déduction pour les modèles de classe
Guides de déduction générés implicitement
Lorsque, dans un cast de style fonction ou dans la déclaration d'une variable, le spécificateur de type consiste uniquement
en le nom d'un modèle de classe primaire
C
(c'est-à-dire qu'il n'y a pas de liste d'arguments de modèle associée), les candidats pour la déduction sont formés comme suit :
-
Si
Cest défini, pour chaque constructeur (ou modèle de constructeur)C idéclaré dans le modèle principal nommé, un modèle de fonction fictifF iest construit, de sorte que toutes les conditions suivantes soient satisfaites :
-
-
Les paramètres de template de
F isont les paramètres de template deCsuivis (siC iest un template de constructeur) par les paramètres de template deC i(les arguments de template par défaut sont également inclus).
-
Les paramètres de template de
|
(depuis C++20) |
-
-
La
liste de paramètres
de
F iest la liste de paramètres deC i. -
Le type de retour de
F iestCsuivi des paramètres template de la classe template entre<>.
-
La
liste de paramètres
de
-
Si
Cn'est pas défini ou ne déclare aucun constructeur, une fonction template fictive supplémentaire est ajoutée, dérivée comme ci-dessus d'un constructeur hypothétiqueC().
-
Dans tous les cas, un modèle de fonction fictif supplémentaire dérivé comme ci-dessus d'un constructeur hypothétique
C(C)est ajouté, appelé le candidat de déduction de copie.
-
Pour chaque
guide de déduction défini par l'utilisateur
G i, une fonction ou un modèle de fonction fictifF iest construit, de telle sorte que toutes les conditions suivantes soient satisfaites :
-
-
La liste de paramètres de
F iest la liste de paramètres deG i. -
Le type de retour de
F iest l'identifiant de template simple deG i. -
Si
G ia des paramètres de template (syntaxe (2) ),F iest un template de fonction, et sa liste de paramètres de template est la liste de paramètres de template deG i. Sinon,F iest une fonction.
-
La liste de paramètres de
template<class T> struct A { T t; struct { long a, b; } u; }; A a{1, 2, 3}; // aggregate deduction candidate: // template<class T> // A<T> F(T, long, long); template<class... Args> struct B : std::tuple<Args...>, Args... {}; B b{std::tuple<std::any, std::string>{}, std::any{}}; // aggregate deduction candidate: // template<class... Args> // B<Args...> F(std::tuple<Args...>, Args...); // type of b is deduced as B<std::any, std::string> |
(depuis C++20) |
Déduction d'arguments de template
et
résolution de surcharge
sont ensuite effectués pour l'initialisation d'un objet fictif de type classe hypothétique, dont les signatures de constructeurs correspondent aux guides (sauf pour le type de retour) dans le but de former un ensemble de surcharge, et l'initialiseur est fourni par le contexte dans lequel la déduction d'arguments de template de classe a été effectuée, sauf que la première phase de
l'initialisation de liste
(considérant les constructeurs de liste d'initialisation) est omise si la liste d'initialisation consiste en une seule expression de type (éventuellement qualifié cv)
U
, où
U
est une spécialisation de
C
ou une classe dérivée d'une spécialisation de
C
.
Ces constructeurs fictifs sont des membres publics du type de classe hypothétique. Ils sont explicites si le guide a été formé à partir d'un constructeur explicite. Si la résolution de surcharge échoue, le programme est mal formé. Sinon, le type de retour de la spécialisation de modèle
F
sélectionnée devient la spécialisation de modèle de classe déduite.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // Un constructeur déclaré : // C1 : UniquePtr(T*); // Ensemble des guides de déduction générés implicitement : // F1 : template<class T> // UniquePtr<T> F(T* p); // F2 : template<class T> // UniquePtr<T> F(UniquePtr<T>); // candidat de déduction de copie // classe imaginaire pour l'initialisation : // struct X // { // template<class T> // X(T* p); // de F1 // // template<class T> // X(UniquePtr<T>); // de F2 // }; // initialisation directe d'un objet X // avec "new double(2.0)" comme initialiseur // sélectionne le constructeur correspondant au guide F1 avec T = double // Pour F1 avec T=double, le type de retour est UniquePtr<double> // résultat : // UniquePtr<double> dp{new auto(2.0)}
Ou, pour un exemple plus complexe (note : "
S::N
" ne compilerait pas : les qualificateurs de résolution de portée ne sont pas quelque chose qui peut être déduit) :
template<class T> struct S { template<class U> struct N { N(T); N(T, U); template<class V> N(V, U); }; }; S<int>::N x{2.0, 1}; // les guides de déduction générés implicitement sont (notez que T est déjà connu comme étant int) // F1: template<class U> // S<int>::N<U> F(int); // F2: template<class U> // S<int>::N<U> F(int, U); // F3: template<class U, class V> // S<int>::N<U> F(V, U); // F4: template<class U> // S<int>::N<U> F(S<int>::N<U>); (candidat de déduction de copie) // La résolution de surcharge pour l'initialisation directe par liste avec "{2.0, 1}" comme initialiseur // choisit F3 avec U=int et V=double. // Le type de retour est S<int>::N<int> // résultat : // S<int>::N<int> x{2.0, 1};
Guides de déduction définis par l'utilisateur
La syntaxe d'un guide de déduction défini par l'utilisateur est la syntaxe d'une déclaration de fonction (template) avec un type de retour final, sauf qu'elle utilise le nom d'un template de classe comme nom de fonction :
explicit
(optionnel)
template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(optionnel)
;
|
(1) | ||||||||
template <
template-parameter-list
>
requires-clause
(optionnel)
explicit (optionnel) template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(optionnel)
;
|
(2) | ||||||||
| template-parameter-list | - | une liste non vide de paramètres de template séparés par des virgules |
| explicit | - |
un
explicit
spécificateur
|
| template-name | - | le nom du template de classe dont les arguments doivent être déduits |
| parameter-list | - | une liste de paramètres (éventuellement vide) |
| simple-template-id | - | un identifiant de template simple |
| requires-clause | - | (depuis C++20) une requires clause |
|
Les paramètres des guides de déduction définis par l'utilisateur ne peuvent pas avoir de types d'espace réservé : la syntaxe de modèle de fonction abrégée n'est pas autorisée. |
(since C++20) |
Les guides de déduction définis par l'utilisateur doivent nommer un modèle de classe et doivent être introduits dans la même portée sémantique du modèle de classe (qui peut être un espace de noms ou une classe englobante) et, pour un modèle de classe membre, doivent avoir le même accès, mais les guides de déduction ne deviennent pas membres de cette portée.
Un guide de déduction n'est pas une fonction et ne possède pas de corps. Les guides de déduction ne sont pas trouvés par recherche de nom et ne participent pas à la résolution de surcharge, sauf pour la résolution de surcharge face à d'autres guides de déduction lors de la déduction des arguments de template de classe. Les guides de déduction ne peuvent pas être redéclarés dans la même unité de traduction pour le même template de classe.
// déclaration du template template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // guide de déduction supplémentaire template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // utilisations container c(7); // OK : déduit T=int en utilisant un guide généré implicitement std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK : déduit T=double container e{5, 6}; // Erreur : il n'y a pas de std::iterator_traits<int>::value_type
Les constructeurs fictifs à des fins de résolution de surcharge (décrits ci-dessus) sont explicites s'ils correspondent à un guide de déduction généré implicitement à partir d'un constructeur explicite ou à un guide de déduction défini par l'utilisateur qui est déclaré explicit . Comme toujours, ces constructeurs sont ignorés dans le contexte d'initialisation par copie :
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // erreur : ne peut pas déduire à partir de la référence rvalue dans #2, // et #1 est explicite, et n'est pas considéré dans l'initialisation par copie. A a2{i, i}; // OK, #1 déduit A<int> et initialise également A a3{0, i}; // OK, #2 déduit A<int> et initialise également A a4 = {0, i}; // OK, #2 déduit A<int> et initialise également template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4 A a5 = {0, 1}; // erreur : #3 déduit A<int&> // et #1 & #2 résultent en des constructeurs de paramètres identiques. A a6{0, 1}; // OK, #4 déduit A<int> et #2 initialise A a7 = {0, i}; // erreur : #3 déduit A<int&> A a8{0, i}; // erreur : #3 déduit A<int&> // Note : vérifier https://github.com/cplusplus/CWG/issues/647, affirmant que // les exemples a7 et a8 sont incorrects, à remplacer potentiellement par //A a7 = {0, i}; // erreur : #2 et #3 correspondent tous deux, la résolution de surcharge échoue //A a8{i,i}; // erreur : #3 déduit A<int&>, // // #1 et #2 déclarent le même constructeur
L'utilisation d'un typedef membre ou d'un alias de modèle dans la liste de paramètres d'un constructeur ou d'un modèle de constructeur ne rend pas, en soi, le paramètre correspondant du guide généré implicitement un contexte non déduit.
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // Le guide de déduction implicite généré à partir de #1 est équivalent à // template<class T, class U> // B(U, T) -> B<T>; // plutôt que // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // qui n'aurait pas été déductible B b{(int*)0, (char*)0}; // OK, déduit B<char*>
Déduction pour les modèles d'alias
Lorsqu'un cast de style fonction ou une déclaration de variable utilise le nom d'un modèle d'alias
template<class T> class unique_ptr { /* ... */ }; template<class T> class unique_ptr<T[]> { /* ... */ }; template<class T> unique_ptr(T*) -> unique_ptr<T>; // #1 template<class T> unique_ptr(T*) -> unique_ptr<T[]>; // #2 template<class T> concept NonArray = !std::is_array_v<T>; template<NonArray A> using unique_ptr_nonarray = unique_ptr<A>; template<class A> using unique_ptr_array = unique_ptr<A[]>; // generated guide for unique_ptr_nonarray: // from #1 (deduction of unique_ptr<T> from unique_ptr<A> yields T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A> yields nothing): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // where argument_of_unique_ptr_nonarray_is_deducible_from can be defined as // template<class> // class AA; // template<NonArray A> // class AA<unique_ptr_nonarray<A>> {}; // template<class T> // concept argument_of_unique_ptr_nonarray_is_deducible_from = // requires { sizeof(AA<T>); }; // generated guide for unique_ptr_array: // from #1 (deduction of unique_ptr<T> from unique_ptr<A[]> yields T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A[]> yields T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // where argument_of_unique_ptr_array_is_deducible_from can be defined as // template<class> // class BB; // template<class A> // class BB<unique_ptr_array<A>> {}; // template<class T> // concept argument_of_unique_ptr_array_is_deducible_from = // requires { sizeof(BB<T>); }; // Use: unique_ptr_nonarray p(new int); // deduced to unique_ptr<int> // deduction guide generated from #1 returns unique_ptr<int> // deduction guide generated from #2 returns unique_ptr<int[]>, which is ignored because // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> is unsatisfied unique_ptr_array q(new int[42]); // deduced to unique_ptr<int[]> // deduction guide generated from #1 fails (cannot deduce A in A(*)[] from new int[42]) // deduction guide generated from #2 returns unique_ptr<int[]> |
(depuis C++20) |
Notes
La déduction d'arguments de modèle de classe est uniquement effectuée si aucune liste d'arguments de modèle n'est présente. Si une liste d'arguments de modèle est spécifiée, la déduction ne se produit pas.
std::tuple t1(1, 2, 3); // OK : déduction std::tuple<int, int, int> t2(1, 2, 3); // OK : tous les arguments sont fournis std::tuple<> t3(1, 2, 3); // Erreur : aucun constructeur correspondant dans tuple<>. // Aucune déduction effectuée. std::tuple<int> t4(1, 2, 3); // Erreur
|
La déduction d'arguments de template de classe pour les agrégats nécessite généralement des guides de déduction définis par l'utilisateur : template<class A, class B> struct Agg { A a; B b; }; // implicitly-generated guides are formed from default, copy, and move constructors template<class A, class B> Agg(A a, B b) -> Agg<A, B>; // ^ This deduction guide can be implicitly generated in C++20 Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide template<class... T> array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>; auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide |
(jusqu'en C++20) |
Les guides de déduction définis par l'utilisateur ne doivent pas nécessairement être des modèles :
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // déduit en S<std::string>
Dans le cadre d'un modèle de classe, le nom du modèle sans liste de paramètres est un nom de classe injecté, et peut être utilisé comme un type. Dans ce cas, la déduction d'arguments de classe ne se produit pas et les paramètres du modèle doivent être fournis explicitement :
template<class T> struct X { X(T) {} template<class Iter> X(Iter b, Iter e) {} template<class Iter> auto foo(Iter b, Iter e) { return X(b, e); // pas de déduction : X est le X<T> actuel } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // doit spécifier ce que nous voulons } auto baz() { return ::X(0); // pas le nom injecté de la classe ; déduit comme X<int> } };
Dans la résolution de surcharge , l'ordre partiel prend la priorité sur le fait qu'un modèle de fonction est généré à partir d'un guide de déduction défini par l'utilisateur : si le modèle de fonction généré à partir du constructeur est plus spécialisé que celui généré à partir du guide de déduction défini par l'utilisateur, celui généré à partir du constructeur est choisi. Parce que le candidat de déduction de copie est généralement plus spécialisé qu'un constructeur d'encapsulation, cette règle signifie que la copie est généralement préférée à l'encapsulation.
template<class T> struct A { A(T, int*); // #1 A(A<T>&, int*); // #2 enum { value }; }; template<class T, int N = T::value> A(T&&, int*) -> A<T>; //#3 A a{1, 0}; // utilise #1 pour déduire A<int> et initialise avec #1 A b{a, 0}; // utilise #2 (plus spécialisée que #3) pour déduire A<int> et initialise avec #2
Lorsque les critères de départage antérieurs, y compris l'ordonnancement partiel, n'ont pas permis de distinguer deux modèles de fonction candidats, les règles suivantes s'appliquent :
- Un modèle de fonction généré à partir d'un guide de déduction défini par l'utilisateur est préféré à celui généré implicitement à partir d'un constructeur ou d'un modèle de constructeur.
- Le candidat de déduction de copie est préféré à tous les autres modèles de fonction générés implicitement à partir d'un constructeur ou d'un modèle de constructeur.
- Un modèle de fonction généré implicitement à partir d'un constructeur non-modèle est préféré à un modèle de fonction généré implicitement à partir d'un modèle de constructeur.
template<class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // #5, le candidat de déduction de copie A(A); A x(1, 2, 3); // utilise #3, généré à partir d'un constructeur non-template template<class T> A(T) -> A<T>; // #6, moins spécialisé que #5 A a(42); // utilise #6 pour déduire A<int> et #1 pour initialiser A b = a; // utilise #5 pour déduire A<int> et #2 pour initialiser template<class T> A(A<T>) -> A<A<T>>; // #7, aussi spécialisé que #5 A b2 = a; // utilise #7 pour déduire A<A<int>> et #1 pour initialiser
Une référence à une valeur de déplacement vers un paramètre de modèle non qualifié cv n'est pas une référence de transfert si ce paramètre est un paramètre de modèle de classe :
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1 : T&& n'est pas une référence de transfert // U&& est une référence de transfert A(T&&, int*); // #2 : T&& n'est pas une référence de transfert }; template<class T> A(T&&, int*) -> A<T>; // #3 : T&& est une référence de transfert int i, *ip; A a{i, 0, ip}; // erreur, ne peut pas déduire à partir de #1 A a0{0, 0, ip}; // utilise #1 pour déduire A<int> et #1 pour initialiser A a2{i, ip}; // utilise #3 pour déduire A<int&> et #2 pour initialiser
Lors de l'initialisation à partir d'un seul argument d'un type qui est une spécialisation du modèle de classe en question, la déduction de copie est généralement préférée à l'encapsulation par défaut :
std::tuple t1{1}; // std::tuple<int> std::tuple t2{t1}; // std::tuple<int>, pas std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, pas std::vector<std::vector<int>> (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
En dehors du cas particulier de la copie par rapport à l'encapsulation, la forte préférence pour les constructeurs de liste d'initialisation dans l'initialisation de liste reste intacte.
std::vector v1{1, 2}; // std::vector<int> std::vector v2(v1.begin(), v1.end()); // std::vector<int> std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>
Avant l'introduction de la déduction d'arguments de modèle de classe, une approche courante pour éviter de spécifier explicitement les arguments consistait à utiliser un modèle de fonction :
std::tuple p1{1, 1.0}; //std::tuple<int, double>, utilisant la déduction auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pré-C++17
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_deduction_guides
|
201703L
|
(C++17) | Déduction d'arguments de template pour les templates de classe |
201907L
|
(C++20) | CTAD pour les agrégats et alias |
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 publié | Comportement corrigé |
|---|---|---|---|
| CWG 2376 | C++17 |
CTAD serait effectué même si le type de la variable déclarée est
différent du modèle de classe dont les arguments seront déduits |
ne pas effectuer
CTAD dans ce cas |
| CWG 2628 | C++20 | les guides de déduction implicites ne propageaient pas les contraintes | propager les contraintes |
| CWG 2697 | C++20 |
il n'était pas clair si la syntaxe abrégée de modèle de fonction
est autorisée dans les guides de déduction définis par l'utilisateur |
interdite |
| CWG 2707 | C++20 | les guides de déduction ne pouvaient pas avoir une clause requires finale | ils le peuvent |
| CWG 2714 | C++17 |
les guides de déduction implicites ne prenaient pas en compte
les arguments par défaut des constructeurs |
les prendre en compte |
| CWG 2913 | C++20 |
la résolution de
CWG issue 2707
a rendu la syntaxe du guide de déduction
incompatible avec la syntaxe de déclaration de fonction |
syntaxe ajustée |
| P0702R1 | C++17 |
un constructeur de liste d'initialisation peut supplanter le
candidat de déduction de copie, entraînant un encapsulage |
phase de liste d'initialisation
ignorée lors de la copie |