Espacios de nombres
Variantes
Acciones

Deducción de argumentos de plantillas de clase (CTAD) (desde C++17)

De cppreference.com
< cpp‎ | language

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>
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 ficticia Fi, tal que
  • los parámetros de plantilla de Fi son los parámetros de plantilla de C seguidos (si Ci es una plantilla de constructor) por los parámetros de plantilla de Ci (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 es C seguido de los parámetros de plantilla de la la plantilla de clase dentro 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ético C()
  • 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.
  • Adicionalmente, si
  • C está definido y satisface los requisitos de un tipo agregado asumiendo que cualquier clase base dependiente no tiene funciones virtuales o clases base virtuales,
  • no hay guías de deducción definidas por usuarios para C, y
  • la variable se inicializa a partir de una lista no vacía de inicializadores arg1, arg2, ..., argn (que puede usar el inicializador designado),
se puede añadir un candidato de deducción agregada. La lista de parámetros del candidato de deducción agregada se produce a partir de los tipos de elementos agregados, de la siguiente manera:
  • Sea ei el elemento agregado (que puede ser recursivo) que se inicializaría a partir de argi, donde
  • si la expansión del paquete es una elemento agregado final, se considera que coincide con todos los elementos restantes de la lista inicializadora;
  • de lo contrario, se considera el paquete vacío.
  • Si no existe tal ei, no se añade el candidato a deducción agregada.
  • De otro modo, se determina la lista de parámetros T1, T2, ..., Tn del candidato a deducción agregada de la siguiente manera:
  • Si ei es un array y argi es una lista de inicialización entre llaves , Ti es una referencia rvalue al tipo declarado de ei.
  • Si ei es un array y argi es un literal cadena, Ti es una referencia lvalue al tipo declarado calificado constante de ei.
  • En otro caso, Ti es el tipo declarado de ei.
  • Si se omite un paquete porque no es un elemento agregado final, se insertar un paquete de parámetros adicional de la forma Pj ... en su posición de elemento agregado original. (Generalmente esto causa que la deducción falle).
  • Si un paquete es una elemento agregado final, la secuencia final de parámetros que le corresponde se reemplaza por un solo parámetro de la forma Tn ....
El candidato de deducción agregada es una plantilla de función ficticia derivada como se indicó anteriormente de una constructor hipotético C(T1, T2, ..., Tn).
Durante la deducción de argumentos de plantilla para el candidato de deducción agregada, el número de elementos en un paquete de parámetros final solo se deduce de la cantidad de argumentos de función restantes si no se deduce de otra forma.
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 alias

Cuando una conversión de estilo función o una declaración de una variable usa el nombre de una plantilla de alias A sin un alista de argumentos como especificador de tipo, donde A se define como un alias de B<ArgList>, el ámbito de B no es dependiente, y B es una plantilla de clase o una plantilla de alias definida de manera similar, la deducción procederá de la misma manera que para las plantillas de clase,excepto que las guías se generan a partir de las guías de B, de la siguiente manera:

  • Para cada guía f de B, se deduce los argumentos de plantilla del tipo de retorno de f de B<ArgList> mediante la deducción de argumentos de plantilla, excepto que la deducción no falla si no se deducen algunos argumentos. Si la deducción falla por otro motivo, continua con un conjunto vacío de argumentos de plantilla deducidos.
  • Se sustituye el resultado de la deducción anterior en f, si la sustitución falla, no se produce ninguna guía; de lo contrario, sea g el resultado de la sustitución, se forma una guía f', tal que
  • Los tipos de parámetros y el tipo de retorno de f' son los mismos que g
  • Si f es una plantilla, f' es una plantilla de función cuya lista de parámetros de plantilla consiste en todos los parámetros de plantilla de A (incluyendo su argumentos de plantilla predeterminados) que aparecen en las deducciones anteriores o (recursivamente) en sus argumentos de plantilla predeterminados, seguidos por los parámetros de plantilla de f que no se dedujeron (incluyendo su argumentos de plantilla por defecto); de lo contrario (f no es una plantilla), f' es una función
  • Las restricciones asociadas de f' son la conjunción de la restricciones asociadas de g y una restricción que se satisface si u solo si los argumentos de A son deducibles del tipo de resultado
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ísticaValorEstándarComentario
__cpp_deduction_guides201703L(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
close