Class template argument deduction (CTAD) (since C++17)
Para instanciar una class template , cada argumento de plantilla debe ser conocido, pero no todos los argumentos de plantilla tienen que ser especificados. En los siguientes contextos el compilador deducirá los argumentos de plantilla a partir del tipo del inicializador:
- cualquier declaración que especifique la inicialización de una variable y variable template, cuyo tipo declarado es la plantilla de clase (posiblemente cv-qualified ):
std::pair p(2, 4.5); // deduce a std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // igual que auto t = std::make_tuple(4, 3, 2.5); std::less l; // igual que std::less<void> l;
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // el tipo asignado es A<int>
auto lck = std::lock_guard(mtx); // deduce a std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // deduce a std::back_insert_iterator<T>, // donde T es el tipo del contenedor vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // deduce a Foo<T>, // donde T es el tipo único de la lambda
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(desde C++20) |
Contenidos |
Deducción para plantillas de clase
Guías de deducción generadas implícitamente
Cuando, en una conversión de estilo función o en la declaración de una variable, el especificador de tipo consiste únicamente
en el nombre de una plantilla de clase primaria
C
(es decir, no hay una lista de argumentos de plantilla adjunta), los candidatos para la deducción se forman de la siguiente manera:
-
Si
Cestá definido, para cada constructor (o plantilla de constructor)C ideclarado en la plantilla primaria nombrada, se construye una función de plantilla ficticiaF i, de modo que se satisfagan todas las siguientes condiciones:
-
-
Los parámetros de plantilla de
F ison los parámetros de plantilla deCseguidos (siC ies una plantilla de constructor) por los parámetros de plantilla deC i(los argumentos de plantilla predeterminados también se incluyen).
-
Los parámetros de plantilla de
|
(desde C++20) |
-
-
La
lista de parámetros
de
F ies la lista de parámetros deC i. -
El tipo de retorno de
F iesCseguido por los parámetros de plantilla de la plantilla de clase encerrados en<>.
-
La
lista de parámetros
de
-
Si
Cno está definido o no declara ningún constructor, se añade una función template ficticia adicional, derivada como se indicó anteriormente de un constructor hipotéticoC().
-
En cualquier caso, se añade una función plantilla ficticia adicional derivada como se describió anteriormente a partir de un constructor hipotético
C(C), denominada el candidato de deducción de copia.
-
Para cada
guía de deducción definida por el usuario
G i, se construye una función o plantilla de función ficticiaF i, de modo que se cumplan todas las siguientes condiciones:
-
-
La lista de parámetros de
F ies la lista de parámetros deG i. -
El tipo de retorno de
F ies el identificador de plantilla simple deG i. -
Si
G itiene parámetros de plantilla (sintaxis (2) ),F ies una plantilla de función, y su lista de parámetros de plantilla es la lista de parámetros de plantilla deG i. De lo contrario,F ies una función.
-
La lista de parámetros 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> |
(desde C++20) |
Deducción de argumentos de plantilla
y
resolución de sobrecarga
se realizan luego para la inicialización de un objeto ficticio de tipo de clase hipotética, cuyas firmas de constructor coinciden con las guías (excepto por el tipo de retorno) con el propósito de formar un conjunto de sobrecarga, y el inicializador es proporcionado por el contexto en el que se realizó la deducción de argumentos de plantilla de clase, excepto que la primera fase de
inicialización de lista
(considerando constructores de lista de inicialización) se omite si la lista de inicialización consiste en una única expresión de tipo (posiblemente calificado cv)
U
, donde
U
es una especialización de
C
o una clase derivada de una especialización de
C
.
Estos constructores ficticios son miembros públicos del tipo de clase hipotético. Son explícitos si la guía se formó a partir de un constructor explícito. Si la resolución de sobrecarga falla, el programa está mal formado. De lo contrario, el tipo de retorno de la especialización de plantilla
F
seleccionada se convierte en la especialización de plantilla de clase deducida.
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // Un constructor declarado: // C1: UniquePtr(T*); // Conjunto de guías de deducción generadas implícitamente: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // candidato de deducción por copia // clase imaginaria para inicializar: // struct X // { // template<class T> // X(T* p); // de F1 // // template<class T> // X(UniquePtr<T>); // de F2 // }; // inicialización directa de un objeto X // con "new double(2.0)" como inicializador // selecciona el constructor que corresponde a la guía F1 con T = double // Para F1 con T=double, el tipo de retorno es UniquePtr<double> // resultado: // UniquePtr<double> dp{new auto(2.0)}
O, para un ejemplo más complejo (nota: "
S::N
" no compilaría: los calificadores de resolución de ámbito no son algo que se pueda deducir):
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}; // las guías de deducción generadas implícitamente son (nótese que T ya se conoce como 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>); (candidato de deducción por copia) // La resolución de sobrecarga para inicialización directa de lista con "{2.0, 1}" como inicializador // elige F3 con U=int y V=double. // El tipo de retorno es S<int>::N<int> // resultado: // S<int>::N<int> x{2.0, 1};
Guías de deducción definidas por el usuario
La sintaxis de una guía de deducción definida por el usuario es la sintaxis de una declaración de función (plantilla) con un tipo de retorno final, excepto que utiliza el nombre de una plantilla de clase como nombre de función:
explicit
(opcional)
template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(opcional)
;
|
(1) | ||||||||
template <
template-parameter-list
>
requires-clause
(opcional)
explicit (opcional) template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(opcional)
;
|
(2) | ||||||||
| template-parameter-list | - | una lista no vacía separada por comas de parámetros de plantilla |
| explicit | - |
un
explicit
especificador
|
| template-name | - | el nombre de la plantilla de clase cuyos argumentos deben deducirse |
| parameter-list | - | una (posiblemente vacía) lista de parámetros |
| simple-template-id | - | un identificador de plantilla simple |
| requires-clause | - | (desde C++20) una requires cláusula |
|
Los parámetros de las guías de deducción definidas por el usuario no pueden tener tipos de marcador de posición: la sintaxis de plantilla de función abreviada no está permitida. |
(since C++20) |
Las guías de deducción definidas por el usuario deben nombrar una plantilla de clase y deben introducirse dentro del mismo ámbito semántico de la plantilla de clase (que podría ser un espacio de nombres o una clase envolvente) y, para una plantilla de clase miembro, deben tener el mismo acceso, pero las guías de deducción no se convierten en miembros de ese ámbito.
Una guía de deducción no es una función y no tiene cuerpo. Las guías de deducción no se encuentran mediante búsqueda por nombre y no participan en la resolución de sobrecarga excepto para la resolución de sobrecarga frente a otras guías de deducción al deducir argumentos de plantilla de clase. Las guías de deducción no pueden ser redeclaradas en la misma unidad de traducción para la misma plantilla de clase.
// declaración de la plantilla template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // guía de deducción adicional template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // usos container c(7); // OK: deduce T=int usando una guía generada implícitamente std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: deduce T=double container e{5, 6}; // Error: no existe std::iterator_traits<int>::value_type
Los constructores ficticios para el propósito de resolución de sobrecarga (descritos anteriormente) son explicit si corresponden a una guía de deducción generada implícitamente formada a partir de un constructor explicit o a una guía de deducción definida por el usuario que se declara explicit . Como siempre, tales constructores se ignoran en contexto de inicialización por copia:
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // error: no se puede deducir desde referencia a rvalue en #2, // y #1 es explícito, y no se considera en inicialización por copia. A a2{i, i}; // OK, #1 deduce A<int> y también inicializa A a3{0, i}; // OK, #2 deduce A<int> y también inicializa A a4 = {0, i}; // OK, #2 deduce A<int> y también inicializa 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}; // error: #3 deduce A<int&> // y #1 & #2 resultan en constructores con mismos parámetros. A a6{0, 1}; // OK, #4 deduce A<int> y #2 inicializa A a7 = {0, i}; // error: #3 deduce A<int&> A a8{0, i}; // error: #3 deduce A<int&> // Nota: consultar https://github.com/cplusplus/CWG/issues/647, que afirma que // los ejemplos a7 y a8 son incorrectos, posiblemente reemplazados por //A a7 = {0, i}; // error: #2 y #3 coinciden ambos, falla la resolución de sobrecarga //A a8{i,i}; // error: #3 deduce A<int&>, // // #1 y #2 declaran el mismo constructor
El uso de un typedef de miembro o una plantilla de alias en la lista de parámetros de un constructor o plantilla de constructor no, por sí mismo, convierte el parámetro correspondiente de la guía generada implícitamente en un contexto no deducible.
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // La guía de deducción implícita generada desde #1 es equivalente a // template<class T, class U> // B(U, T) -> B<T>; // en lugar de // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // que no habría sido deducible B b{(int*)0, (char*)0}; // OK, deduce B<char*>
Deducción para plantillas de alias
Cuando una conversión de estilo función o declaración de variable utiliza el nombre de una plantilla de 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[]> |
(desde C++20) |
Notas
La deducción de argumentos de plantilla de clase solo se realiza si no está presente una lista de argumentos de plantilla. Si se especifica una lista de argumentos de plantilla, la deducción no tiene lugar.
std::tuple t1(1, 2, 3); // OK: deducción std::tuple<int, int, int> t2(1, 2, 3); // OK: todos los argumentos están proporcionados std::tuple<> t3(1, 2, 3); // Error: no hay constructor coincidente en tuple<>. // No se realizó deducción. std::tuple<int> t4(1, 2, 3); // Error
|
La deducción de argumentos de plantilla de clase para agregados normalmente requiere guías de deducción definidas por el usuario: 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 |
(hasta C++20) |
Las guías de deducción definidas por el usuario no tienen que ser plantillas:
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // deducido a S<std::string>
Dentro del ámbito de una plantilla de clase, el nombre de la plantilla sin una lista de parámetros es un nombre de clase inyectado, y puede usarse como un tipo. En ese caso, la deducción de argumentos de clase no ocurre y los parámetros de plantilla deben proporcionarse explícitamente:
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); // sin deducción: X es el X<T> actual } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // debe especificar lo que queremos } auto baz() { return ::X(0); // no es el nombre de clase inyectado; se deduce como X<int> } };
En la resolución de sobrecarga , el ordenamiento parcial tiene precedencia sobre si una plantilla de función se genera a partir de una guía de deducción definida por el usuario: si la plantilla de función generada a partir del constructor es más especializada que la generada a partir de la guía de deducción definida por el usuario, se elige la generada a partir del constructor. Debido a que el candidato de deducción de copia es típicamente más especializado que un constructor de envoltura, esta regla significa que la copia generalmente se prefiere sobre la envoltura.
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}; // utiliza #1 para deducir A<int> e inicializar con #1 A b{a, 0}; // utiliza #2 (más especializada que #3) para deducir A<int> e inicializar con #2
Cuando los desempates anteriores, incluido el ordenamiento parcial, no logran distinguir entre dos plantillas de función candidatas, se aplican las siguientes reglas:
- Se prefiere una plantilla de función generada a partir de una guía de deducción definida por el usuario sobre una generada implícitamente a partir de un constructor o plantilla de constructor.
- El candidato de deducción por copia se prefiere sobre todas las demás plantillas de función generadas implícitamente a partir de un constructor o plantilla de constructor.
- Se prefiere una plantilla de función generada implícitamente a partir de un constructor no plantilla sobre una plantilla de función generada implícitamente a partir de una plantilla de constructor.
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, el candidato de deducción de copia A(A); A x(1, 2, 3); // usa #3, generado desde un constructor no plantilla template<class T> A(T) -> A<T>; // #6, menos especializada que #5 A a(42); // usa #6 para deducir A<int> y #1 para inicializar A b = a; // usa #5 para deducir A<int> y #2 para inicializar template<class T> A(A<T>) -> A<A<T>>; // #7, tan especializada como #5 A b2 = a; // usa #7 para deducir A<A<int>> y #1 para inicializar
Una referencia a valor del lado derecho a un parámetro de plantilla no calificado cv no es una referencia de reenvío si ese parámetro es un parámetro de plantilla de clase:
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& no es una referencia de reenvío // U&& es una referencia de reenvío A(T&&, int*); // #2: T&& no es una referencia de reenvío }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& es una referencia de reenvío int i, *ip; A a{i, 0, ip}; // error, no se puede deducir desde #1 A a0{0, 0, ip}; // usa #1 para deducir A<int> y #1 para inicializar A a2{i, ip}; // usa #3 para deducir A<int&> y #2 para inicializar
Al inicializar desde un único argumento de un tipo que es una especialización de la plantilla de clase en cuestión, la deducción por copia generalmente se prefiere sobre el encapsulamiento por defecto:
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, no std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, no std::vector<std::vector<int>> (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
Fuera del caso especial para copiar vs. envolver, la fuerte preferencia por los constructores de lista de inicializadores en la inicialización de lista permanece intacta.
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>
Antes de que se introdujera la deducción de argumentos de plantilla de clase, un enfoque común para evitar especificar explícitamente los argumentos es utilizar una plantilla de función:
std::tuple p1{1, 1.0}; //std::tuple<int, double>, usando deducción auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-C++17
| Macro de prueba de características | Valor | Estándar | Característica |
|---|---|---|---|
__cpp_deduction_guides
|
201703L
|
(C++17) | Deducción de argumentos de plantilla para plantillas de clase |
201907L
|
(C++20) | CTAD para agregados y alias |
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 2376 | C++17 |
CTAD se realizaría incluso si el tipo de la variable declarada es
diferente de la plantilla de clase cuyos argumentos se deducirán |
no realizar
CTAD en este caso |
| CWG 2628 | C++20 | las guías de deducción implícitas no propagaban restricciones | propagar restricciones |
| CWG 2697 | C++20 |
no estaba claro si la sintaxis abreviada de plantilla de función
está permitida en guías de deducción definidas por el usuario |
prohibida |
| CWG 2707 | C++20 | las guías de deducción no podían tener una cláusula requires final | sí pueden |
| CWG 2714 | C++17 |
las guías de deducción implícitas no consideraban
los argumentos predeterminados de los constructores |
considerarlos |
| CWG 2913 | C++20 |
la resolución de
CWG issue 2707
hizo que la sintaxis de guía de deducción
fuera inconsistente con la sintaxis de declaración de función |
ajustó la sintaxis |
| P0702R1 | C++17 |
un constructor de lista de inicialización puede adelantarse al
candidato de deducción de copia, resultando en encapsulamiento |
fase de lista de inicialización
omitida al copiar |