Template argument deduction
Para instanciar una function template , cada argumento de plantilla debe ser conocido, pero no todos los argumentos de plantilla tienen que ser especificados. Cuando es posible, el compilador deducirá los argumentos de plantilla faltantes a partir de los argumentos de la función. Esto ocurre cuando se intenta una llamada a función, cuando se toma la dirección de una function template, y en algunos otros contextos :
template<typename To, typename From> To convert(From f); void g(double d) { int i = convert<int>(d); // llama a convert<int, double>(double) char c = convert<char>(d); // llama a convert<char, double>(double) int(*ptr)(float) = convert; // instancia convert<int, float>(float) // y almacena su dirección en ptr }
Este mecanismo hace posible utilizar operadores de plantilla, ya que no existe una sintaxis para especificar argumentos de plantilla para un operador que no sea reescribiéndolo como una expresión de llamada a función:
La deducción de argumentos de plantilla tiene lugar después de la búsqueda de nombre de la plantilla de función (que puede involucrar búsqueda dependiente de argumento ) y antes de la sustitución de argumentos de plantilla (que puede involucrar SFINAE ) y la resolución de sobrecarga .
|
La deducción de argumentos de plantilla también se realiza cuando el nombre de una plantilla de clase se utiliza como el tipo de un objeto que se está construyendo: 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 deducción de argumentos de plantilla para plantillas de clase tiene lugar en declaraciones y en expresiones de conversión explícita; consulte deducción de argumentos de plantilla de clase para más detalles. |
(desde C++17) |
Contenido
Deducción a partir de una llamada a funciónLa deducción de argumentos de plantilla intenta determinar los argumentos de plantilla (tipos para parámetros de plantilla de tipo Si hay múltiples parámetros, cada par
Si template<class T>
int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = overload set
// P = T(*)(T), A1 = int(int): deduced T = int
// P = T(*)(T), A2 = int(char): fails to deduce T
// only one overload works, deduction succeeds
Antes de que comience la deducción, se realizan los siguientes ajustes a 1) Si
P no es un tipo de referencia,a) si
A es un tipo de arreglo, A se reemplaza por el tipo de puntero obtenido de la conversión de arreglo a puntero;b) de lo contrario, si
A es un tipo de función, A se reemplaza por el tipo de puntero obtenido de la conversión de función a puntero;c) de lo contrario, si
A es un tipo cv-calificado, los cv-calificadores de nivel superior se ignoran para la deducción:
template<class T>
void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
2) Si
P es un tipo cv-calificado, los cv-calificadores de nivel superior se ignoran para la deducción.3) Si
P es un tipo de referencia, el tipo referenciado se usa para la deducción.4) Si
P es una referencia rvalue a un parámetro de plantilla sin cv-calificar (llamadas forwarding references), y el argumento de llamada a función correspondiente es un lvalue, el tipo lvalue referencia a A se usa en lugar de A para la deducción (Nota: esta es la base para la acción de std::forward. Nota: en class template argument deduction, el parámetro de plantilla de una plantilla de clase nunca es una forwarding reference(desde C++17)):
template<class T>
int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
int main()
{
int i;
int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
// int n3 = g(i); // error: deduces to g<int>(const int&&), which
// cannot bind an rvalue reference to an lvalue
}
Después de estas transformaciones, la deducción procede como se describe a continuación (cf. sección deducción a partir de un tipo) e intenta encontrar tales argumentos de plantilla que hagan que el Si la deducción habitual de 1) Si
P es un tipo de referencia, el A deducido (es decir, el tipo al que se refiere la referencia) puede tener más cv-calificaciones que el A transformado:
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) El
A transformado puede ser otro tipo de puntero o puntero a miembro que se pueda convertir al A deducido mediante una conversión de calificaciones o una conversión de puntero a función(desde C++17):
template<typename T>
void f(const T*);
int* p;
f(p); // P = const T*, A = int*:
// deduced T = int, deduced A = const int*
// qualification conversion applies (from int* to const int*)
3) Si
P es una clase y P tiene la forma simple-template-id (nombre de plantilla con una lista de argumentos de plantilla) o splice specifier con una lista de argumentos de plantilla(desde C++26), entonces el A transformado puede ser una clase derivada del A deducido. De manera similar, si P es un puntero a una clase de la forma simple-template-id (nombre de plantilla con una lista de argumentos de plantilla) o splice specifier con una lista de argumentos de plantilla(desde C++26), el A transformado puede ser un puntero a una clase derivada apuntada por el A deducido:
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>&, adjusted to P = B<T> (a simple-template-id), A = D<int>:
// deduced T = int, deduced A = B<int>
// A is derived from deduced A
}
Contextos no deducidos
En los siguientes casos, los tipos, plantillas y constantes que se utilizan para componer
1)
El
nested-name-specifier
(todo lo que está a la izquierda del operador de resolución de ámbito
::
) de un tipo que fue especificado usando un
qualified-id
:
// the identity template, often used to exclude specific arguments from deduction // (available as std::type_identity as of 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: deduced T = std::complex<double> // P2 = T, A2 = double // P2/A2: deduced T = double // error: deduction fails, T is ambiguous good(x, 1.2); // P1 = std::vector<T>, A1 = std::vector<std::complex<double>> // P1/A1: deduced T = std::complex<double> // P2 = identity<T>::type, A2 = double // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2 // OK: T = std::complex<double>
4)
Un argumento de plantilla constante o un límite de array en el cual una subexpresión hace referencia a un parámetro de plantilla:
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 es contexto no deducible, N no puede deducirse // nota: f(std::array<int, N> a) podría deducir N
5)
Un parámetro de plantilla utilizado en el tipo de parámetro de un parámetro de función que tiene un argumento predeterminado que se está utilizando en la llamada para la cual se está realizando la deducción de argumentos:
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)
El parámetro
P
, cuyo
A
es una función o un conjunto de sobrecargas tal que más de una función coincide con
P
o ninguna función coincide con
P
o el conjunto de sobrecargas incluye una o más plantillas de función:
7)
El parámetro
P
, cuyo
A
es una lista de inicialización entre llaves, pero
P
no es
std::initializer_list
, una referencia a uno (posiblemente calificado con cv), o una referencia a un array}}:
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á en contexto no deducido // error: T no está especificado explícitamente o deducido de otro P/A g2({1, 2, 3}, 10); // P1 = std::vector<T>, A1 = {1, 2, 3}: T está en contexto no deducido // P2 = T, A2 = int: T deducido = int
10)
Para
P
de tipo array (pero no referencia a array o puntero a array), el límite principal del array:
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: deduced i = 20 f1<20>(a); // OK f2(a); // error: i is non-deduced context f2<10>(a); // OK f3(a); // OK: deduced i = 10 f3<10>(a); // OK }
En cualquier caso, si cualquier parte de un nombre de tipo es no deducida, todo el nombre de tipo es contexto no deducido. Sin embargo, los tipos compuestos pueden incluir tanto nombres de tipo deducidos como no deducidos. Por ejemplo, en
A
<
T
>
::
B
<
T2
>
,
Deducción de un tipo
Dado un parámetro de función
En las formas anteriores,
Si
Si
Los formularios pueden anidarse y procesarse de forma recursiva:
Si un parámetro de plantilla constante de una plantilla de función se utiliza en la lista de parámetros de plantilla de un parámetro de función (que también es una plantilla), y el argumento de plantilla correspondiente se deduce, el tipo del argumento de plantilla deducido (como se especifica en su lista de parámetros de plantilla envolvente, lo que significa que las referencias se conservan) debe coincidir exactamente con el tipo del parámetro de plantilla constante, excepto que se eliminan los calificadores cv, y excepto cuando el argumento de plantilla se deduce de un límite de array—en ese caso se permite cualquier tipo integral, incluso bool aunque siempre se convertiría en true : template<int i> class A {}; template<short s> void f(A<s>); // el tipo del parámetro de plantilla constante es short void k1() { A<1> a; // el tipo del parámetro de plantilla constante de a es int f(a); // P = A<(short)s>, A = A<(int)1> // error: el argumento de plantilla constante deducido no tiene el mismo // tipo que su argumento de plantilla correspondiente f<1>(a); // OK: el argumento de plantilla no se deduce, // esto llama a 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> // el tipo del parámetro es int& // el tipo del argumento es int& en la declaración de plantilla de struct X // OK (con CWG 2091): deduce que R hace referencia a n } El parámetro de plantilla de tipo no puede deducirse del tipo de un argumento predeterminado de función: template<typename T> void f(T = 5, T = 7); void g() { f(1); // OK: llama a f<int>(1, 7) f(); // error: no se puede deducir T f<int>(); // OK: llama a f<int>(5, 7) } La deducción de parámetros de plantilla de plantilla puede utilizar el tipo utilizado en la especialización de plantilla utilizada en la llamada a la función: template<template<typename> class X> struct A {}; // A es una plantilla con un parámetro 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 deducido = B, llama a f(A<B>) Otros contextosAdemás de las llamadas a funciones y las expresiones de operadores, la deducción de argumentos de plantilla se utiliza en las siguientes situaciones:
Resolución de sobrecarga
La deducción de argumentos de plantilla se utiliza durante la
resolución de sobrecarga
, al generar especializaciones a partir de una función de plantilla candidata.
std::string s; std::getline(std::cin, s); // "std::getline" nombra 4 plantillas de función, // 2 de las cuales son funciones candidatas (número correcto de parámetros) // Primera plantilla candidata: // P1 = std::basic_istream<CharT, Traits>&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // la deducción determina los parámetros de plantilla de tipo CharT, Traits y Allocator // especialización std::getline<char, std::char_traits<char>, std::allocator<char>> // Segunda plantilla candidata: // P1 = std::basic_istream<CharT, Traits>&&, A1 = std::cin // P2 = std::basic_string<CharT, Traits, Allocator>&, A2 = s // la deducción determina los parámetros de plantilla de tipo CharT, Traits y Allocator // especialización std::getline<char, std::char_traits<char>, std::allocator<char>> // la resolución de sobrecarga clasifica el enlace de referencia desde el lvalue std::cin // y selecciona la primera de las dos especializaciones candidatas Si la deducción falla, o si la deducción tiene éxito, pero la especialización que produce sería inválida (por ejemplo, un operador sobrecargado cuyos parámetros no son tipos de clase ni enumeración), la especialización no se incluye en el conjunto de sobrecarga, similar a SFINAE . Dirección de un conjunto de sobrecargaLa deducción de argumentos de plantilla se utiliza al tomar la dirección de un conjunto sobrecargado , que incluye plantillas de función.
El tipo de función de la plantilla de función es
std::cout << std::endl; // std::endl nombra una plantilla de función // tipo de endl P = // std::basic_ostream<CharT, Traits>& (std::basic_ostream<CharT, Traits>&) // parámetro operator<< A = // std::basic_ostream<char, std::char_traits<char>>& (*)( // std::basic_ostream<char, std::char_traits<char>>& // ) // (otras sobrecargas de operator<< no son viables) // la deducción determina los parámetros de plantilla de tipo CharT y Traits
Se aplica una regla adicional a la deducción en este caso: al comparar parámetros de función
Ordenamiento parcialLa deducción de argumentos de plantilla se utiliza durante el ordenamiento parcial de plantillas de función sobrecargadas .
Plantilla de función de conversiónLa deducción de argumentos de plantilla se utiliza al seleccionar user-defined conversion function los argumentos de plantilla.
Si
a)
si el
P
es un tipo de arreglo, entonces el tipo de puntero obtenido mediante la conversión de arreglo a puntero se utiliza en lugar de
P
;
b)
si el
P
es un tipo de función, entonces el tipo de puntero a función obtenido mediante la conversión función-a-puntero se utiliza en lugar de
P
;
c)
si
P
está calificado con cv, se ignoran los calificadores cv de nivel superior.
Si
Si la deducción habitual de
a)
si
A
es un tipo de referencia,
A
puede estar más calificado con cv que el
A
deducido;
b)
si
A
es un tipo puntero o puntero a miembro, se permite que el
A
deducido sea cualquier puntero que pueda convertirse a
A
mediante conversión de calificación:
struct C { template<class T> operator T***(); }; C c; const int* const* const* p1 = c; // P = T***, A = const int* const* const* // la deducción regular de llamada a función para // template<class T> void f(T*** p) como si se llamara con el argumento // de tipo const int* const* const* falla // la deducción adicional para funciones de conversión determina T = int // (el A deducido es int***, convertible a const int* const* const*)
Consulte member template para otras reglas relacionadas con las plantillas de función de conversión. Instanciación explícitaLa deducción de argumentos de plantilla se utiliza en instanciaciones explícitas , especializaciones explícitas , y aquellas declaraciones friend donde el declarator-id se refiere a una especialización de una plantilla de función (por ejemplo, friend ostream & operator << <> ( ... ) ), si no todos los argumentos de plantilla están explícitamente especificados o tienen valores por defecto, se utiliza la deducción de argumentos de plantilla para determinar a qué especialización de plantilla se hace referencia.
template<class X> void f(X a); // 1ra plantilla f template<class X> void f(X* a); // 2da plantilla f template<> void f<>(int* a) {} // especialización explícita de f // P1 = void(X), A1 = void(int*): X deducido = int*, f<int*>(int*) // P2 = void(X*), A2 = void(int*): X deducido = int, f<int>(int*) // f<int*>(int*) y f<int>(int*) se someten luego a ordenamiento parcial // que selecciona f<int>(int*) como la plantilla más especializada
Se aplica una regla adicional a la deducción en este caso: al comparar parámetros de función
Plantilla de función de desasignación
La deducción de argumentos de plantilla se utiliza al determinar si una
función de desasignación
especialización de plantilla coincide con una forma de colocación dada de
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; // cuando X() lanza, se busca operator delete // P1 = void(void*, T), A1 = void(void*, bool): // T deducido = bool // P2 = void(void*, T), A2 = void(void*, double): // T deducido = double // la resolución de sobrecarga selecciona operator delete<bool> } catch(const std::exception&) {} try { X* p1 = new (13.2) X; // misma búsqueda, selecciona operator delete<double> } catch(const std::exception&) {} } Plantillas de aliasLas plantillas de alias no se deducen , excepto en deducción de argumentos de plantilla de clase (desde 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); // CORRECTO: TT deducido = vector template<template<class> class TT> void f(TT<int>); f(v); // error: TT no puede deducirse como "Vec" porque Vec es una plantilla de alias Conversiones implícitas
La deducción de tipos no considera conversiones implícitas (aparte de los ajustes de tipo listados anteriormente): esa es la tarea de la
resolución de sobrecarga
, que ocurre posteriormente. Sin embargo, si la deducción tiene éxito para todos los parámetros que participan en la deducción de argumentos de plantilla, y todos los argumentos de plantilla que no se deducen están explícitamente especificados o tienen valores por defecto, entonces los parámetros de función restantes se comparan con los argumentos de función correspondientes. Para cada parámetro restante
Los parámetros con tipos dependientes en los que ningún parámetro de plantilla participa en la deducción de argumentos de plantilla, y los parámetros que se volvieron no dependientes debido a la sustitución de argumentos de plantilla explícitamente especificados serán verificados durante la resolución de sobrecarga: 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); // para #1, la deducción determina T = struct A, pero el argumento restante 1 // no puede convertirse implícitamente a su parámetro void*: la deducción falla // la instanciación del tipo de retorno no se solicita // para #2, la deducción determina T = struct A, y el argumento restante 1 // puede convertirse implícitamente a su parámetro int: la deducción tiene éxito // la llamada a función se compila como una llamada a #2 (el fallo de deducción es SFINAE) } Informes de defectosLos siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
|