Deducción de argumentos de plantillas de clase (CTAD) (desde C++17)
Para crear una instancia de una plantilla de clase, se deben conocer todos los argumentos de la plantilla, pero no es necesario especificar todos los argumentos de la plantilla. En los siguientes contextos, el compilador deducirá los argumentos de la plantilla a partir del tipo de inicializador:
- Cualquier declaración que especifique la inicialización de una variable y plantilla de variable, cuyo tipo declarado es la plantilla de clase (que puede estar calificado cv):
std::pair p(2, 4.5);// se deduce en 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};// tipo asignado es A<int>
- Expresiones de conversión estilo función:
auto lck =std::lock_guard(mtx);// se deduce en std::lock_guard<std::mutex>std::copy_n(vi1, 3, std::back_insert_iterator(vi2));// se deduce en std::back_insert_iterator<T>,// donde T es el tipo del contenedor vi2std::for_each(vi.begin(), vi.end(), Foo([&](int i){...}));// se deduce en Foo<T>,// donde T es el tipo lambda único
template<class T>struct X {constexpr X(T){} auto operator<=>(const X&)const=default;}; template<X x>struct Y {}; Y<0> y;// correcto, Y<X<int>(0)> | (desde C++20) |
Contenido |
[editar]Deducción para plantillas de clase
[editar]Guías de deducción implícitamente generadas
Cunado, en una conversión de estilo de 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), los candidatos para la deducción se forman se la siguiente manera:
- Si se define
C
, para cada constructor (o plantilla de constructor)Ci
declarado en la plantilla principal nombrada, se construye una plantilla de función ficticiaFi
, tal que
- los parámetros de plantilla de
Fi
son los parámetros de plantilla deC
seguidos (siCi
es una plantilla de constructor) por los parámetros de plantilla deCi
(también se incluyen los argumentos de plantilla por defecto) - los parámetros de función de
Fi
son los parámetros del constructor - el tipo de retorno de
Fi
esC
seguido de los parámetros de plantilla de la la plantilla de clase dentro de<>
- los parámetros de plantilla de
- Si
C
no está definido o no declara ningún constructor, se añade una plantilla de función ficticia adicional, derivada como se señaló anteriormente de una constructor hipotéticoC()
- En cualquier caso, se añade una plantilla de función ficticia adicional derivada como se indicó anteriormente a partir de un constructor hipotético
C(C)
, denominado candidato de deducción de copia.
template<class T>struct A { T t; struct{long a, b;} u;}; A a{1, 2, 3};// candidata de deducción agregada:// 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{}};// candidata de deducción agregada:// template<class... Args>// B<Args...> F(std::tuple<Args...>, Args...); // el tipo de b se deduce comos B<std::any, std::string> | (desde C++20) |
Luego se realiza la deducción de argumentos de plantilla y la resolución de sobrecarga para la inicialización de un objeto ficticio de tipo de clase hipotético, cuyas signaturas de constructor coinciden con las guías (excepto el tipo de retorno) con el fin de formar un conjunto de sobrecarga, el contexto proporciona el inicializador en el que se realizó la deducción de argumento de plantilla, excepto quet la primera fase de la inicialización de lista (considerando los constructores de lista de inicializadores) se omiten si la lista de inicializadores consta de una sola expresión de tipo (que puede estar calificado cv) U
, donde U
es una especialización de C
o una clase derivada de una especialización de C
.
Esos constructores ficticios son miembros públicos del tipo de clase hipotética. Son explícitos si se formó la guía desde un constructor explícito. Si falla la resolución de sobrecarga, el programa está mal formado. En otro caso, 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>); // candidata deducción de copia // clase imaginaria para iniciar:// struct X// {// template<class T>// X(T *p); // desde F1// // template<class T>// X(UniquePtr<T>); // desde F2// }; // la inicialización directa de un objeto X // con "new double(2.0)" como inicializador// elige el constructor que corresponde al aguí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 se 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 (tenga en cuenta que ya se sabe que T es 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>); (candidata deducción de copia) // La resolución de sobrecarga para la inicialización de lista directa 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};
[editar]Guías de deducción definidas por el usuario
La sintaxis de una guía de deducción definida por usuario es la sintaxis de una declaración de función con un tipo de retorno final, excepto que usa el nombre de una plantilla de clase como el nombre de función:
especificador-explícito(opcional)nombre-plantilla( cláusula-de-declaración-de-parámetros) -> id-plantilla-simple; | |||||||||
Las guías de usuario definidas por el usuario deben denominar una plantilla de clase y deben introducirse dentro del mismo ámbito semántico de la plantilla de clase (que puede ser un espacio de nombres o una clase que la encierre) y, para una plantilla de clase miembro, deben tener el mismo acceso, pero las guías de deducción no forman parte 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 la búsqueda de nombres y participan en la resolución de sobrecarga, excepto la resolución de sobrecarga en comparación con otras guías de deduccion al deducir argumentos de plantilla de clase. Las guías de deducción no se pueden redeclarar en la misma unidad de traducción para la misma plantilla de clase.
// declaración de la plantillatemplate<class T>struct contenedor { contenedor(T t){} template<class Iter> contenedor(Iter inicio, Iter fin);}; // guía adicional de deduccióntemplate<class Iter> contenedor (Iter i, Iter f)-> contenedor<typenamestd::iterator_traits<Iter>::value_type>; // uso contenedor c(7);// correcto: se deduce T=int usando una guía generada implícitamentestd::vector<double> v ={/* ... */};auto d = contenedor(v.begin(), v.end());// correcto: se deduce T=double contenedor e{5, 6};// Error: no hay std::iterator_traits<int>::value_type
Los constructores ficticios a efectos de resolución de sobrecarga (descritos anteriormente) son explícitos si corresponden a una guía de deducción generada implícitamente formada a partir de un constructor explícito o una guía de deducción definida por usuario que se declara explícita. Como siempre, dichos constructores se ignoran en el contexto de inicialización de 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 de una referencia rvalue en #2,// y #1 es explicita, y no se considera en la inicialización de copia. A a2{i, i};// correcto, #1 deduce A<int> y también inicializa A a3{0, i};// correcto, #2 deduce A<int> y también inicializa A a4 ={0, i};// correcto, #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 y #2 dan como resultado los mismos constructores de parámetros. A a6{0, 1};// correcto, #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&>
El uso de un miembro typedef o una plantilla de alias es un constructor o en la lista de parámetros de una plantilla de constructor no convierte, por sí mismo, el parámetro correspondiente de la guía generada implícitamente en un contexto no deducido.
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 por #1 es la equivalente a// template<class T, class U>// B(U, T) -> B<T>;// en ves de// template<class T, class U>// B(U, typename B<T>::template TA<U>) -> B<T>;// que no hubiera sido deducible B b{(int*)0, (char*)0};// correcto, se deduce B<char*>
Deducción para plantillas de aliasCuando una conversión de estilo función o una declaración de una variable usa 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[]>; // guía generada para unique_ptr_nonarray: // desde #1 (la deducción de unique_ptr<T> a través de unique_ptr<A> da T = A):// template<class A>// requires(argumento_de_unique_ptr_nonarray_es_deducible_de<unique_ptr<A>>)// auto F(A*) -> unique_ptr<A>; // desde #2 (la deducción de unique_ptr<T[]> a través de unique_ptr<A> no da nada):// template<class T>// requires(argumento_de_unique_ptr_nonarray_es_deducible_de<unique_ptr<T[]>>)// auto F(T*) -> unique_ptr<T[]>; // donde argumento_de_unique_ptr_nonarray_es_deducible_de se puede definir como // template<class>// class AA; // template<NonArray A>// class AA<unique_ptr_nonarray<A>> {}; // template<class T>// concept argumento_de_unique_ptr_nonarray_es_deducible_de =// requires { sizeof(AA<T>); }; // la guía generada para unique_ptr_array: // desde #1 (la deducción de unique_ptr<T> a través de unique_ptr<A[]> da T = A[]):// template<class A>// requires(argumento_de_unique_ptr_array_es_deducible_de<unique_ptr<A[]>>)// auto F(A(*)[]) -> unique_ptr<A[]>; // desde #2 (la deducción de unique_ptr<T[]> a través de unique_ptr<A[]> da T = A):// template<class A>// requires(argumento_de_unique_ptr_array_es_deducible_de<unique_ptr<A[]>>)// auto F(A*) -> unique_ptr<A[]>; // donde argumento_de_unique_ptr_array_es_deducible_de se puede definir como // template<class>// class BB; // template<class A>// class BB<unique_ptr_array<A>> {}; // template<class T>// concept argumento_de_unique_ptr_array_es_deducible_de =// requires { sizeof(BB<T>); }; // Uso: unique_ptr_nonarray p(new int);// se deduce a unique_ptr<int>// la guía de deducción generada desde #1 devuelve unique_ptr<int>// la guía de deducción generada desde #2 devuelve unique_ptr<int[]>, que se ignora porque// no se satisface argumento_de_unique_ptr_nonarray_es_deducible_de<unique_ptr<int[]>> unique_ptr_array q(new int[42]);// se deduce a unique_ptr<int[]>// la guía de deducción generada desde #1 falla (no se puede deducir A en A(*)[] desde new int[42])// la guía de deducción generada desde #2 devuelve unique_ptr<int[]> | (desde C++20) |
[editar]Notas
La deducción de argumentos de plantilla de clase solo se realiza si no hay una lista de argumentos de plantilla presente. Si se especifica una lista de argumentos de plantilla, la deducción no tiene lugar.
std::tuple t1(1, 2, 3);// correcto: deducciónstd::tuple<int, int, int> t2(1, 2, 3);// correcto: se proporcionan todos los argumentos std::tuple<> t3(1, 2, 3);// Error: no coincide ningún constructor en tuple<>.// No se hace deducción.std::tuple<int> t4(1, 2, 3);// Error
La deducción de argumentos de plantilla de clase de agregados generalmente requiere guías de deducción definidas por usuario: template<class A, class B>struct Agg { A a; B b;};// las guiás generadas implícitamente se formas a partir de constructores predeterminados, de copia y de movimiento template<class A, class B> Agg(A a, B b)-> Agg<A, B>;// ^ Esta guiá de deducción se puede generar implícitamente en C++20 Agg agg{1, 2.0};// se deduce Agg<int, double> a partir de la guiá definida por usuario template<class... T> array(T&&... t)-> array<std::common_type_t<T...>, sizeof...(T)>;auto a = array{1, 2, 5u};// se deduce array<unsigned, 3> a partir de la guía definida por usuario | (hasta C++20) |
Las guías de deducción definidas por usuario no tienen que ser plantillas:
template<class T>struct S { S(T);}; S(charconst*)-> S<std::string>; S s{"hello"};// se deduce S<std::string>
Dentro del ámbito de una plantilla de clase, el nombre dela plantilla sin una lista de parámetros es un nombre de plantilla inyectado y se puede usar como un tipo. En este caso, la deducción del argumento de clase no ocurre y los parámetros de la 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);// no hay 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);// debemos especificar lo que queremos} auto baz(){return::X(0);// no el nombre de clase inyectada; se deduce que es X<int>}};
En la resolución de sobrecarga, el ordenamiento parcial tiene prioridad sobre si una plantilla de función se genera a partir de una guiá de deducción definida por usuario: si la plantilla de función generada por el constructor es más especializada que la generada por la guía de deducción definida por usuario, se selecciona la generada por el constructor. Debido a que el candidato de deducción de copia suele ser más especializado que un constructor de envoltura, esta regla significa que generalmente se prefiere la copia a 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};// usa #1 para deducir A<int> e inicializar con with #1 A b{a, 0};// usa #2 (más especializada que #3) para deducir A<int> e inicializar con #2
Cuando los desempates anteriores, incluida la ordenación parcial, no consiguieron distinguir entre dos plantillas de funciones candidatas, se aplican las siguientes reglas:
- Se prefiere una plantilla de función generada por una guiá de deducción definida por usuario sobre una generada implícitamente a partir de un constructor o plantilla de constructor.
- Se prefiere el candidato de deducción de copia sobre todas las demás plantillas de función generadas implícitamente a partir de un constructor o una 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, la candidata de deducción de copia A(A); A x(1, 2, 3);// usa #3, generada a partir de 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 rvalue a un parámetro de plantilla no calificado cv no es una referencia adelantada 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 adelantada// U&& es una referencia adelantada A(T&&, int*);// #2: T&& no es una referencia adelantada}; template<class T> A(T&&, int*)-> A<T>;// #3: T&& es una referencia adelantada int i, *ip; A a{i, 0, ip};// error, no se puede deducir a partir de #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
Cuando se inicializa a partir de un solo argumento de un tipo que es una especialización de la plantilla de clase en cuestión, generalmente se prefiere la deducción de copia sobre la envoltura 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 de copia frente a envoltura, la gran 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 la especificación explícita de argumentos es usar una plantilla de función:
std::tuple p1{1, 1.0};//std::tuple<int, double>, usando deducciónauto p2 =std::make_tuple(1, 1.0);//std::tuple<int, double>, antes de C++17
Prueba de característica | Valor | Estándar | Comentario |
---|---|---|---|
__cpp_deduction_guides | 201703L | (C++17) | Deducción de argumentos de plantilla para plantillas de clase |
201907L | (C++20) | CTAD para agregados y alias |
[editar]Informe de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 2376 | C++17 | CTAD se realiza incluso si el tipo de variable declarada es diferente de la plantilla de clase cuyos argumentos se deducirán | no se hace CTAD en este caso |
P0702R1 | C++17 | un constructor de lista de inicializadores puede adelantarse al candidato de deducción de copia, resultado una envoltura | se omite la lista de inicializadores al copiar |