Plantilla de función
Una plantilla de función define una familia de funciones.
[editar] Sintaxis
template < lista-de-parámetros > declaración-de-función
|
(1) | ||||||||
template < lista-de-parámetros > requires restricción declaración-de-función
|
(2) | (desde C++20) | |||||||
declaración-de-función-con-marcador-de-posición | (3) | (desde C++20) | |||||||
export template < lista-de-parámetros > declaración-de-función
|
(4) | (eliminado en C++11) | |||||||
[editar] Explicación
lista-de-parámetros | - | Una lista no vacía separada por comas de los parámetros de plantilla, cada uno de los cuales es o bien un parámetro de no-tipo, un parámetro de tipo, un parámetro de plantilla, o un paquete de parámetros de cualquiera de esos (desde C++11). Al igual que cualquier plantilla, los parámetros pueden restringirse (desde C++20) |
declaración-de-función | - | Una declaración de función. El nombre de la función declarada se vuelve un nombre de plantilla. |
restricción(C++20) | - | Una expresión de restricción que restringe los parámetros de plantilla aceptados por esta plantilla de función. |
declaración-de-función-con- marcadores-de-posición(desde C++20) |
- | Una declaración de función donde el tipo de al menos un parámetro usa el marcador de posición auto o Concept auto: la lista de parámetros de plantilla tendrá un parámetro inventado para cada marcador de posición (véase Plantilla de función abreviada más adelante). |
|
(hasta C++11) |
Plantilla de función abreviadaCuando los tipos marcadores de posición (ya sea auto o Concept auto) aparecen en la lista de parámetros de una declaración de función o de una declaración de plantilla de función, la declaración declara una plantilla de función, y se añade un parámetro de plantilla inventado para cada marcador de posición a la lista de parámetros de plantilla: void f1(auto); // lo mismo que template<class T> void f(T) void f2(C1 auto); // lo mismo que template<C1 T> void f2(T), si C1 es un concepto void f3(C2 auto...); // lo mismo que template<C2... Ts> void f3(Ts...), si C2 es un concepto void f4(const C3 auto*, C4 auto&); // lo mismo que template<C3 T, C4 U> void f4(const T*, U&); template <class T, C U> void g(T x, U y, C auto z); // lo mismo que template<class T, C U, C W> void g(T x, U y, W z); Al igual que todas las plantillas de función, las plantillas de función abreviadas pueden especializarse. template<> void f4<int>(const int*, const double&); // especialización de f4<int, const double>
|
(desde C++20) |
[editar] Instanciación de plantilla de función
Una plantilla de función en sí misma no es un tipo, o una función, o cualquier otra entidad. No se genera código a partir de un archivo fuente que contiene solamente definiciones de plantilla. Para que aparezca cualquier código, una plantilla debe instanciarse: los argumentos de plantilla deben determinarse para que el compilador pueda generar una función real (o clase, de una plantilla de clase).
[editar] Instanciación explícita
template tipo-de-retorno nombre < lista-de-argumentos > ( lista-de-parámetros ) ;
|
(1) | ||||||||
template tipo-de-retorno nombre ( lista-de-parámetros ) ;
|
(2) | ||||||||
extern template tipo-de-retorno nombre < lista-de-argumentos > ( lista-de-parámetros ) ;
|
(3) | (desde C++11) | |||||||
extern template tipo-de-retorno nombre ( lista-de-parámetros ) ;
|
(4) | (desde C++11) | |||||||
Una definición de instanciación explícita fuerza la instanciación de la función o función miembro a la que se refiere. Puede aparecer en el programa en cualquier lugar después de la definición de la plantilla, y para una lista de argumentos dada, solo se permite que aparezca una vez en el programa, no requiere diagnóstico.
Una declaración de instanciación explícita (una plantilla externa) evita las instancias implícitas: el código que de otro modo causaría una instanciación implícita debe usar la definición de instanciación explícita proporcionada en otro lugar del programa. |
(desde C++11) |
Un argumento de plantilla final (trailing) puede dejarse sin especificar en una instanciación explícita de una especialización de plantilla de función o de una especialización de plantilla de función miembro si puede ser deducido del parámetro de función.
template<typename T> void f(T s) { std::cout << s << '\n'; } template void f<double>(double); // instancia a f<double>(double) template void f<>(char); // instancia a f<char>(char), argumento de plantilla deducido template void f(int); // instancia a f<int>(int), argumento de plantilla deducido
La instanciación explícita de una plantilla de función o de una función miembro de una plantilla de clase no puede usar inline
o constexpr
. Si la declaración de la instanciación explícita denomina a una función miembro especial declarada implícitamente, el programa está mal formado.
La instanciación explícita de un constructor no puede usar una lista de parámetros de plantilla (sintaxis (1)), que nunca es necesario porque puede deducirse (sintaxis (2)).
La instanciación explícita de un destructor potencial debe denominar el destructor seleccionado de la clase. |
(desde C++20) |
Las declaraciones de instanciación explícita no suprimen la instanciación implícita de funciones inline, declaraciones auto, referencias y especializaciones de plantillas de clase. Por lo tanto, cuando la función en línea que es objeto de una declaración de instanciación explícita tiene uso ODR, se crea una instancia implícita para que sea en línea, pero su copia que no es en línea no se genera en esta unidad de traducción.
La definición de instanciación explícita de una plantilla de función con argumentos por defecto no es un uso de los argumentos, y no intenta inicializarlos:
char* p = 0; template<class T> T g(T x = &p) { return x; } template int g<int>(int); // de acuerdo incluso cuando &p no es un int.
[editar] Instanciación implícita
Cuando el código se refiere a una función en un contexto que requiere que exista la definición de la función, o si la existencia de la definición afecta a la semántica del programa (desde C++11), y esta función en particular no se ha instanciado explícitamente, se produce una instanciación implícita. La lista de argumentos de plantilla no se tiene que proporcionar si se puede deducir del contexto.
#include <iostream> template<typename T> void f(T s) { std::cout << s << '\n'; } int main() { f<double>(1); // instancia y llama a f<double>(double) f<>('a'); // instancia y llama a f<char>(char) f(7); // instancia y llama a f<int>(int) void (*ptr)(std::string) = f; // instancia a f<string>(string) pf("∇"); // llama a f<string>(string) }
La existencia de una definición de función se considera que afecta a la semántica del programa si la función es necesaria para la evaluación contante por una expresión, incluso si no se requiere una evaluación constante de la expresión o si la expresión constante no usa la definición. template<typename T> constexpr int f() { return T::value; } template<bool B, typename T> void g(decltype(B ? f<T>() : 0)); template<bool B, typename T> void g(...); template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})); template<bool B, typename T> void h(...); void x() { g<false, int>(0); // Correcto: B ? f<T>() : 0 no es potencialmente evaluada constante h<false, int>(0); // error: instancia f<int> aunque B se evalúa como falso // y la inicialización de lista de int desde int no se puede contraer } |
(desde C++11) |
Nota: Omitir completamente <>
permite a la resolución de sobrecarga que examine tanto las sobrecargas de plantilla como las de no plantilla.
[editar] Deducción de argumentos de plantilla
Para crear una instancia de una plantilla de función, se deben conocer todos los argumentos de la plantilla, pero no se deben especificar todos los argumentos de la plantilla. Cuando sea posible, el compilador deducirá los argumentos de plantilla faltantes de los argumentos de la función. Esto ocurre cuando se intenta una llamada a función y cuando se toma la dirección de una plantilla de función.
template<typename A, typename Desde> A convertir(Desde f); void g(double d) { int i = convertir<int>(d); // llama a convertir<int,double>(double) char c = convertir<char>(d); // llama a convertir<char,double>(double) int(*ptr)(float) = convertir; // instancia a convertir<int, float>(float) }
Este mecanismo hace posible utilizar operadores de plantilla, ya que no hay 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 toma lugar después de la búsqueda de nombre de la plantilla de función (que puede conllevar búsqueda dependiente de argumento) y antes de la resolución de sobrecarga.
Véase deducción de argumentos de plantilla para más detalles.
[editar] Argumentos de plantilla explícitos
Los argumentos de plantilla de una plantilla de función pueden obtenerse de las siguientes maneras:
- la deducción de argumentos de plantilla;
- los argumentos de plantilla por defecto;
- especificados explícitamente, que puede hacerse en los siguientes contextos:
- en una expresión de llamada a función;
- cuando se toma la dirección de una función;
- cuando se inicializa una referencia a una función;
- cuando se forma un puntero a una función miembro;
- en una especialización explícita;
- en una instanciación explícita;
- en una declaración
friend
.
No existe una manera de especificar explícitamente los parámetros de plantilla a los operadores sobrecargados, las funciones de conversión, y los constructores, ya que se llaman sin el uso del nombre de la función.
Los argumentos de plantilla especificados deben coincidir con los parámetros de plantilla en la especie (p. ej., de tipo con de tipo, sin tipo con sin tipo, y de plantilla con de plantilla). No puede haber más argumentos que parámetros (a menos que un parámetro sea un paquete de parámetros, en cuyo caso debe haber un argumento para cada parámetro que no sea de paquete) (desde C++11).
Los argumentos de no-tipo especificados deben o bien coincidir con los tipos de los parámetros de plantilla de no-tipo correspondientes, o ser convertibles a ellos.
Los parámetros de función que no participan en la deducción de argumentos de plantilla (p. ej., si los argumentos de plantilla correspondientes se especifican explícitamente) están sujetos a las conversiones implícitas del tipo del parámetro de función correspondiente (como en la resolución de sobrecarga habitual).
Un paquete de parámetros de plantilla que se especifica explícitamente puede extenderse por la deducción de argumentos de plantilla si existen argumentos adicionales: template<class ... Types> void f(Types ... values); void g() { f<int*, float*>(0, 0, 0); // Types = {int*, float*, int} } |
(desde C++11) |
[editar] Sustitución de argumentos de plantilla
Cuando todos los argumentos de plantilla se han especificado, deducido u obtenido a partir de argumentos de plantilla por defecto, cada uso de un parámetro de plantilla en la lista de parámetros de función se reemplaza con los argumentos de plantilla correspondientes.
El fallo de sustitución (es decir, falla al reemplazar los parámetros de plantilla con los argumentos de plantilla deducidos o proporcionados) de una plantilla de función elimina la plantilla de función del conjunto de sobrecarga. Esto permite varias formas de manipular conjuntos de sobrecarga mediante la metaprogramación de plantillas: véase SFINAE para más detalles.
Después de la sustitución, todos los parámetros de función de tipo array y tipo función se ajustan a punteros, y todos los calificadores-cv de nivel superior se eliminan de los parámetros de función (como en una declaración de función normal.
La eliminación de los calificadores cv de nivel superior no afecta al tipo de parámetro tal como aparece dentro de la función:
template <class T> void f(T t); template <class X> void g(const X x); template <class Z> void h(Z z, Z* zp); // dos funciones distintas con el mismo tipo, pero // dentro de la función, t tiene distintas calificaciones cv f<int>(1); // el tipo de la función es void(int), t es int f<const int>(1); // el tipo de la función es void(int), t es const int // dos funciones distintas con el mismo tipo y la misma x // (los punteros a estas dos funciones no son iguales, // y las variables estáticas locales de la función tendrían distintas direcciones) g<int>(1); // el tipo de la función es void(int), x es const int g<const int>(1); // el tipo de la función es void(int), x es const int // solo los calificadores-cv de nivel superior se retiran: h<const int>(1, NULL); // el tipo de la función es void(int, const int*) // z es const int, zp es const int*
[editar] Sobrecarga de plantillas de función
Las plantillas de función y las funciones que no son de plantilla pueden estar sobrecargadas.
Una función que no es de plantilla siempre es distinta de una especialización de plantilla con el mismo tipo. Las especializaciones de diferentes plantillas de función siempre son distintas entre sí, incluso si tienen el mismo tipo. Dos plantillas de función con el mismo tipo de retorno y la misma lista de parámetros son distintas y se pueden distinguir con una lista explícita de argumentos de plantilla.
Cuando una expresión que usa parámetros de plantilla de tipo o de no-tipo aparece en la lista de parámetros de función o en el tipo de retorno, esa expresión permanece como parte de la signatura de la plantilla de función con el propósito de sobrecargar:
template<int I, int J> A<I+J> f(A<I>, A<J>); // sobrecarga #1 template<int K, int L> A<K+L> f(A<K>, A<L>); // igual que #1 template<int I, int J> A<I-J> f(A<I>, A<J>); // sobrecarga #2
Dos expresiones que involucran parámetros de plantilla se denominan equivalentes si dos definiciones de función que contienen estas expresiones serían las mismas bajo las reglas de una definición (ODR), es decir, las dos expresiones contienen la misma secuencia de símbolos cuyos nombres se resuelven en las mismas entidades mediante la búsqueda de nombres, excepto que los parámetros de plantilla pueden tener un nombre diferente. Dos expresiones lambda nunca son equivalentes. (desde C++20)
template <int I, int J> void f(A<I+J>); // sobrecarga de plantilla #1 template <int K, int L> void f(A<K+L>); // equivalente a #1
Al determinar si dos expresiones dependientes son equivalentes, solamente se consideran los nombres dependientes involucrados, no los resultados de la búsqueda de nombres. Si múltiples declaraciones de la misma plantilla difieren en el resultado de la búsqueda de nombres, se usa la primera de tales declaraciones:
template <class T> decltype(g(T())) h(); // decltype(g(T())) es un tipo dependiente int g(int); template <class T> decltype(g(T())) h() // redeclaración de h() usa una búsqueda previa { return g(T()); // aunque aquí la búsqueda encuentra a g(int) } int i = h<int>(); // falla en la sustitución de argumentos de plantilla; g(int) // no estaba en ámbito en la primera declaración de h()
Dos plantillas de función se consideran equivalentes si
- están declaradas en el mismo ámbito;
- tienen el mismo nombre;
- tienen listas de parámetros de plantilla equivalentes, que quiere decir que las listas son de la misma longitud, y para cada par de parámetros correspondiente, todo lo siguiente es verdadero:
- los dos parámetros son de la misma especie (ambos de tipo, de no-tipo, o de plantilla);
|
(desde C++11) |
- si son no-tipo, sus tipos son equivalentes;
- si son de plantilla, sus parámetros de plantilla son equivalentes;
|
(desde C++20) |
- las expresiones que involucran parámetros de plantilla en sus tipos de retorno y las listas de parámetros son equivalentes
|
(desde C++20) |
Dos expresiones potencialmente evaluadas (desde C++20) que involucran parámetros de plantilla se denominan funcionalmente equivalentes si no son equivalentes, pero para cualquier conjunto dado de argumentos de plantilla, la evaluación de las dos expresiones da como resultado el mismo valor.
Dos plantillas de función se consideran funcionalmente equivalentes si son "equivalentes", excepto que una o más expresiones que involucran parámetros de plantilla en sus tipos de retorno y listas de parámetros son funcionalmente equivalentes.
Además, dos plantillas de función son funcionalmente equivalentes pero no equivalentes si sus restricciones se especifican de manera diferente, pero aceptan y se satisfacen por el mismo conjunto de listas de argumentos de plantilla. |
(desde C++20) |
Si un programa contiene declaraciones de plantillas de función que son funcionalmente equivalentes pero no equivalentes, el programa está mal formado; no se requiere diagnóstico.
// equivalente template <int I> void f(A<I>, A<I+10>); // sobrecarga de #1 template <int I> void f(A<I>, A<I+10>); // redeclaración de sobrecarga de #1 // no es equivalente template <int I> void f(A<I>, A<I+10>); // sobrecarga de #1 template <int I> void f(A<I>, A<I+11>); // sobrecarga de #2 // funcionalmente equivalente, pero no es equivalente; // este programa está mal formado, no se requiere diagnóstico template <int I> void f(A<I>, A<I+10>); // sobrecarga de #1 template <int I> void f(A<I>, A<I+1+2+3+4>); // funcionalmente equivalente
Cuando la misma especialización de plantilla de función coincide con más de una plantilla de función sobrecargada (con frecuencia esto resulta de la deducción de argumentos de plantilla), se realiza la ordenación parcial de plantillas de función sobrecargadas para seleccionar la mejor coincidencia.
Específicamente, la ordenación parcial tiene lugar en las siguientes situaciones:
template<class X> void f(X a); template<class X> void f(X* a); int* p; f(p);
template<class X> void f(X a); template<class X> void f(X* a); void (*p)(int*) = &f;
Esta sección está incompleta Razón: mini-example |
template<class X> void f(X a); // primera plantilla f template<class X> void f(X* a); // segunda plantilla f template<> void f<>(int *a) {} // especialización explícita // la deducción de argumentos de plantilla resulta con dos candidatos: // f<int*>(int*) y f<int>(int*) // el ordenamiento parcial selecciona a f<int>(int*) como más especializada
Informalmente, "A es más especializada que B" significa que "A acepta menos tipos que B".
Formalmente, para determinar cuál de las dos plantillas de función es más especializada, el proceso de ordenamiento parcial primero transforma una de las dos plantillas de la siguiente manera:
- Para cada parámetro de tipo, no-tipo y tipo de plantilla, incluidos los paquetes de parámetros, (desde C++11) se genera un tipo, valor o plantilla ficticio único y se sustituye en el tipo de función de la plantilla.
- Si solo una de las dos plantillas de función que se compara es una función miembro , y esa plantilla de función es un miembro no estático de alguna clase
A
, se inserta un nuevo primer parámetro en su lista de parámetros. Tomando cv como los calificadores-cv de la plantilla de función y ref como el calificador-de-referencia de la plantilla de función (desde C++11), el tipo del nuevo parámetros es “cv”A&
a no ser que ref sea&&
, o ref no esté presente y el primer parámetro de la otra plantilla tenga un tipo referencia rvalue, ene este caso el tipo es cvA&&
(desde C++11). Esto ayuda al ordenamiento de los operadores, que se buscan como funciones miembro y no miembro:
struct A {}; template<class T> struct B { template<class R> int operator*(R&); // #1 }; template<class T, class R> int operator*(T&, R&); // #2 int main() { A a; B<A> b; b * a; // deducción de argumentos de plantilla para int B<A>::operator*(R&) da R=A // para int operator*(T&, R&), T=B<A>, R=A // Para el propósito de ordenamiento parcial, la plantilla miembro B<A>::operator* // se transforma en template<class R> int operator*(B<A>&, R&); // el ordenamiento parcial entre int operator*( T&, R&) T=B<A>, R=A // e int operator*(B<A>&, R&) R=A selecciona a int operator*(B<A>&, A&) // como más especializada }
Después de que una de las dos plantilla se transforme como se describió anteriormente, se ejecuta la deducción de argumentos de plantilla usando la plantilla transformada como plantilla de argumento y y el tipo de la plantilla original de la otra plantilla como plantilla de parámetro. Luego, el proceso se repite usando la segunda plantilla (después de la transformaciones) como argumento y la primera plantilla en su forma original como parámetro.
Los tipos que se usan para determinar el orden dependen del contexto:
- en el contexto de una llamada a función, los tipos son aquellos tipos de parámetros de función para los cuales la llamada de función tiene argumentos (argumentos de función por defecto, paquete de parámetros, (desde C++11) y los parámetros de puntos suspensivos no se consideran – véase ejemplos a continuación)
- en el contexto de una llamada a una función de conversión definida por el usuario, se usan los tipos de retorno de las plantillas de función de conversión
- en otros contexto, se usa el tipo de la plantilla de función
Se deduce cada tipo de la lista anterior de la plantilla de parámetros. Antes de que empiece la deducción, cada parámetro P
de la plantilla de parámetros y el argumento A
correspondiente de la plantilla de argumentos se ajusta de la siguiente manera:
- Si tanto
P
comoA
son tipos referencia antes, determinar cuál está más cualificado-cv (en los demás casos, se ignoran las calificaciones-cv para propósitos de orden parcial) - Si
P
es un tipo referencia, se reemplaza por el tipo al que refiere - Si
A
es un tipo referencia, se reemplaza por el tipo al que refiere - Si
P
está calificada-cv, se reemplazaP
por su versión sin calificación-cv - Si
A
está calificada-cv, se reemplazaA
por su versión sin calificación-cv
Después de estos ajustes, la deducción de P
de A
se hace siguiendo la deducción de argumento de plantilla de un tipo.
Si Si |
(desde C++11) |
Si el argumento A
de la plantilla 1 transformada se puede utilizar para deducir el parámetro P
correspondiente de la plantilla 2, pero no al revés, entonces esta A
es más especializada que P
con respecto al tipo o tipos que se deducen mediante este par P/A
.
Si la deducción tiene éxito en ambas direcciones, y los P
y A
originales eran tipos referencia, entonces se hacen pruebas adicionales:
- Si
A
era una referencia lvalue yP
una referencia rvalue, se considera queA
es más especializado queP
- Si
A
estaba más calificado-cv queP
, se considera queA
es más especializado queP
En los demás casos, ninguna de las plnatilla es más especializada que la otra con respecto al los tipos deducidos por el par P/A
.
Después de considerar cada P
y A
en ambas direcciones, si, para cada tipo que se consideró,
- la plantilla 1 es al menos tan especializada como la plantilla 2 para todos los tipos
- la plantilla 1 es más especializada que plantilla 2 para algunos tipos
- la plantilla 2 no está más especializada que la plantilla 1 para ningún tipo, o, no está al menos tan especializado para ningún tipo
Entonces la plantilla 1 es más especializada que la plantilla 2. Si las condiciones anteriores se cumplen después de cambiar el orden de las plantillas, la plantilla 2 es más especializada que la plantilla 1. En otro caso, ninguna plantilla es más especializada que la otra.
En caso de empate, si una plantilla de función tiene un paquete de parámetros final y la otra no, la que tiene al parámetro omitido se considera más especializada que la que tiene el paquete de parámetros vacío. |
(desde C++11) |
Si, después de considerar todos los pares de plantillas sobrecargadas, hay una que es inequívocamente más especializada que las demás, se selecciona la especialización de ese plantilla, en otro caso, la compilación falla.
En los siguientes ejemplos, los argumentos ficticios se llamarán U1, U2:
template<class T> void f(T); // plantilla #1 template<class T> void f(T*); // plantilla #2 template<class T> void f(const T*); // plantilla #3 void m() { const int* p; f(p); // selecciones de resolución de sobrecarga: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // ordenamiento parcial: // #1 de transformado #2: void(T) desde void(U1*): P=T, A=U1*: deducción correcta: T=U1* // #2 de transformado #1: void(T*) desde void(U1): P=T*, A=U1: fallo deducción // #2 es más especializado que #1 con respecto a T // #1 de transformado #3: void(T) desde void(const U1*): P=T, A=const U1*: correcto // #3 de transformado #1: void(const T*) desde void(U1): P=const T*, A=U1: fallo // #3 es más especializado que #1 con respecto a T // #2 de transformado #3: void(T*) desde void(const U1*): P=T*, A=const U1*: correcto // #3 de transformado #2: void(const T*) desde void(U1*): P=const T*, A=U1*: fallo // #3 es más especializado que #2 con respecto a T // resultado: se selecciona #3 // en otras palabras, f(const T*) es más especializada que f(T) y f(T*) }
template<class T> void f(T, T*); // #1 template<class T> void f(T, int*); // #2 void m(int* p) { f(0, p); // deducción para #1: void f(T, T*) [T = int] // deducción para #2: void f(T, int*) [T = int] // ordenamiento parcial: // #1 desde #2: void(T,T*) desde void(U1,int*): P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: falla // #2 desde #1: void(T,int*) desde void(U1,U2*): P1=T, A1=U1: T=U1 // P2=int*, A2=U2*: falla // ninguna es más especializada respecto a T, la llamada es ambigua }
template<class T> void g(T); // plantilla #1 template<class T> void g(T&); // plantilla #2 void m() { float x; g(x); // deducción de #1: void g(T ) [T = float] // deducción de #2: void g(T&) [T = float] // ordenamiento parcial: // #1 desde #2: void(T) desde void(U1&): P=T, A=U1 (después de ajustes), correcto // #2 desde #1: void(T&) desde void(U1): P=T (después de ajustes), A=U1: correcto // ninguna es más especializada respecto a T, la llamada es ambigua }
template<class T> struct A { A(); }; template<class T> void h(const T&); // #1 template<class T> void h(A<T>&); // #2 void m() { A<int> z; h(z); // deducción de #1: void h(const T &) [T = A<int>] // deducción de #2: void h(A<T> &) [T = int] // ordenamiento parcial: // #1 desde #2: void(const T&) desde void(A<U1>&): P=T, A=A<U1>: correcto T=A<U1> // #2 desde #1: void(A<T>&) desde void(const U1&): P=A<T>, A=const U1: falla // #2 es más especializado que #1 respecto a T const A<int> z2; h(z2); // deducción de #1: void h(const T&) [T = A<int>] // deducción de #2: void h(A<T>&) [T = int], pero la sustitución falla // solo una sobrecarga para elegir, no se prueba orden parcial, se llama a #1 }
Como un contexto de llamada considera solo los parámetros para los que hay argumentos de llamada explícitos, esos paquetes de parámetros de función, (desde C++11) parámetros de puntos suspensivos, y parámetros con argumentos por defecto, para los que no hay argumento de llamada explícito, se ignoran:
template<class T> void f(T); // #1 template<class T> void f(T*, int = 1); // #2 void m(int* ip) { int* ip; f(ip); // llama a #2 (T* es más especializado que T) }
template<class T> void g(T); // #1 template<class T> void g(T*, ...); // #2 void m(int* ip) { g(ip); // llama a #2 (T* es más especializada que T) }
template<class T, class U> struct A {}; template<class T, class U> void f(U, A<U, T>* p = 0); // #1 template<class U> void f(U, A<U, U>* p = 0); // #2 void h() { f<int>(42, (A<int, int>*)0); // llama a #2 f<int>(42); // error: ambiguo }
template<class T> void g(T, T = T()); // #1 template<class T, class... U> void g(T, U...); // #2 void h() { g(42); // error: ambiguo }
template<class T, class... U> void f(T, U...); // #1 template<class T> void f(T); // #2 void h(int i) { f(&i); // llama a #2 debido al desempate entre el paquete de parámetros y ningún parámetro // (nota: era ambiguo entre DR692 y DR1395) }
template<class T, class... U> void g(T*, U...); // #1 template<class T> void g(T); // #2 void h(int i) { g(&i); // correcto: llama a #1 (T* es más especializada que T) }
template<class... T> int f(T*...); // #1 template<class T> int f(const T&); // #2 f((int*)0); // correcto: seleccionas #2; la plantilla no variádica es más especializada que // la plantilla variádica (era ambiguo antes de DR1395 debido a que la deducción // fallaba en ambas direcciones)
template<class... Args> void f(Args... args); // #1 template<class T1, class... Args> void f(T1 a1, Args... args); // #2 template<class T1, class T2> void f(T1 a1, T2 a2); // #3 f(); // llama a #1 f(1, 2, 3); // llama a #2 f(1, 2); // llama a #3; la plantilla no variádica #3 es más // especializada que las plantillas variádicas #1 y #2
Durante la deducción de argumentos de plantilla dentro del proceso de ordenación parcial, no es necesario que los parámetros de la plantilla coincidan con los argumentos, si el argumento no se usa en ninguno de los tipos considerados para la ordenación parcial
template<class T> T f(int); // #1 template<class T, class U> T f(U); // #2 void g() { f<int>(1); // la especialización de #1 es explícita: T f(int) [T = int] // la especialización de #2 se deduce: T f(U) [T = int, U = int] // ordenación parcial (considerando solamente el tipo de argumento): // #1 desde #2: T(int) desde U1(U2): falla // #2 desde #1: T(U) desde U1(int): correcto: U=int, T sin uso // llama a #1 }
La ordenación parcial de plantillas de función que contienen paquetes de parámetros de plantilla es independiente del número de argumentos deducidos para esos paquetes de parámetros de plantilla. template<class...> struct Tuple {}; template<class... Types> void g(Tuple<Types...>); // #1 template<class T1, class... Types> void g(Tuple<T1, Types...>); // #2 template<class T1, class... Types> void g(Tuple<T1, Types&...>); // #3 g(Tuple<>()); // llama a #1 g(Tuple<int, float>()); // llama a #2 g(Tuple<int, float&>()); // llama a #3 g(Tuple<int>()); // llama a #3 |
(desde C++11) |
Esta sección está incompleta Razón: 14.8.3[temp.over] |
Al compilar una llamada a una plantilla de función, el compilador tiene que decidir entre las sobrecargas que no son de plantilla, las sobrecargas de plantilla y las especializaciones de las sobrecargas de plantilla.
template<class T> void f(T); // #1: sobrecarga de plantilla template<class T> void f(T*); // #2: sobrecarga de plantilla void f(double); // #3: sobrecarga de no plantilla template<> void f(int); // #4: especialización de #1 f('a'); // llama a #1 f(new int(1)); // llama a #2 f(1.0); // llama a #3 f(1); // llama a #4
[editar] Sobrecargas de funciones frente a especializaciones de funciones
Tenga en cuenta que solo las sobrecargas de plantilla primarias y que no son de plantilla participan en la resolución de sobrecarga. Las especializaciones no son sobrecargas y no se consideran. Solo después de que la resolución de sobrecarga selecciona la plantilla de función primaria con mejor coincidencia, se examinan sus especializaciones para ver si hay una mejor coincidencia.
template< class T > void f(T); // #1: sobrecarga para todos los tipos template<> void f(int*); // #2: especialización de #1 para punteros a int template< class T > void f(T*); // #3: sobrecarga para todos los tipos puntero f(new int(1)); // llama a #3, incluso cuando la especialización de #1 // sería una coincidencia perfecta
Es importante recordar esta regla al ordenar los archivos de encabezado de una unidad de traducción. Para obtener más ejemplos de la interacción entre sobrecargas de funciones y especializaciones de funciones, expande a continuación:
Ejemplos |
---|
Considera primero algunos escenarios en los que no se emplea la búsqueda dependiente de argumentos. Para eso, usamos la llamada (f)(t). Como se describe en ADL, poner el nombre de la función entre paréntesis suprime la búsqueda dependiente de argumentos.
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // sobrecarga #1 antes del PDR de f() template<class T> void f(T*) {std::cout << "#2\n";} // sobrecarga #2 antes del PDR de f() template<class T> void g(T* t) { (f)(t); // PDR de f() } int main() { A *p = nullptr; g(p); // PDR de g() y f() } // Tanto #1 como #2 se añaden a la lista de candidatos; // Se selecciona #2 ya que es una mejor coincidencia. Salida: #2
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { (f)(t); // PDR de f() } template<class T> void f(T*) {std::cout << "#2\n";} // #2 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // Solamente se añade #1 a la lista de candidatos; #2 se define // después del PDR; por lo tanto, no se considera para la // sobrecarga incluso si es una mejor coincidencia. Salida: #1
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { (f)(t); // PDR de f() } template<> void f<>(A*) {std::cout << "#3\n";} // #3 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // Se añade #1 a la lista de candidatos; #3 es una mejor coincidencia // definida después del PDR. La lista de candidatos consta de #1, que se selecciona // eventualmente. Después de eso, se selecciona la especialización explícita #3 de #1 // declarada después del PDR porque es una mejor coincidencia. // Este comportamiento se gobierna por 14.7.3/6 [temp.expl.spec] y no // tiene nada que ver con la ADL. Salida: #3
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { (f)(t); // PDR de f() } template<class T> void f(T*) {std::cout << "#2\n";} // #2 template<> void f<>(A*) {std::cout << "#3\n";} // #3 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // #1 es el único miembro de la lista de candidatos y eventualmente // se selecciona. Después de eso, la especialización explícita #3 // se salta porque en realidad especializa a #2 declarada después del PDR. Salida: #1 Ahora consideremos esos casos que emplean la búsqueda dependiente de argumentos (p. ej., usamos el formato de llamada más común f(t)).
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { f(t); // PDR de f() } template<class T> void f(T*) {std::cout << "#2\n";} // #2 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // Se añade a #1 a la lista de candidatos como resultado de la búsqueda ordinaria; // Se define a #2 después del PDR pero se añade a la lista de candidatos vía búsqueda ADL. // Se selecciona a #2 siendo la mejor coincidencia. Salida: #2
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { f(t); // PDR de f() } template<> void f<>(A*) {std::cout << "#3\n";} // #3 template<class T> void f(T*) {std::cout << "#2\n";} // #2 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // Se añade a #1 a la lista de candidatos como resultado de la búsqueda ordinaria; // Se define a #2 después del PDR pero se añade a la lista de candidatos vía búsqueda ADL. // Se selecciona a #2 de entre las plantilla primarias, siendo la mejor coincidencia. // Ya que #3 se declara antes que #2, es una especialización explícita de #1. // Por lo tanto la selección final es #2. Salida: #2
Ejecuta este código #include <iostream> struct A{}; template<class T> void f(T) {std::cout << "#1\n";} // #1 template<class T> void g(T* t) { f(t); // PDR de f() } template<class T> void f(T*) {std::cout << "#2\n";} // #2 template<> void f<>(A*) {std::cout << "#3\n";} // #3 int main() { A *p=nullptr; g(p); // PDR de g() y f() } // Se añade a #1 a la lista de candidatos como resultado de la búsqueda ordinaria; // Se define a #2 después del PDR pero se añade a la lista de candidatos vía búsqueda ADL. // Se selecciona a #2 de entre las plantilla primarias, siendo la mejor coincidencia. // Ya que #3 se declara después que #2, es una especialización explícita de #2. // por lo tanto, se selecciona como la función a llamar. Salida: #3 Siempre que los argumentos sean algunos tipos básicos de C++, no existen espacios de nombres asociados con ADL. Por lo tanto, esos escenarios son idénticos con los ejemplos anteriores que no son de ADL. |
Para reglas detalladas sobre la resolución de sobrecarga, véase resolución de sobrecarga.
[editar] Especialización de plantillas de función
Esta sección está incompleta Razón: 14.8[temp.fct.spec] (observa que 14.8.1[temp.arg.explicit] ya es un artículo de especialización total: o bien lo específico para funciones va aquí (falta de parciales, interacciones con sobrecargas de funciones), o simplemente referirnos a él |
[editar] Informes 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 214 | C++98 | no se especificaba el procedimiento exacto de la ordenación parcial | especificación añadida |
CWG 532 | C++98 | no se especificó el orden entre una plantilla de función miembro no estática y una plantilla de función no miembro |
especificación añadida |
CWG 581 | C++98 | se permitía una lista de argumentos de plantilla en una especialización explícita o en la instanciación de una plantilla de constructor |
prohibido |
CWG 1321 | C++98 | no estaba claro si los mismos nombres depecientes en la primera declaración y una nueva declaración son equivalentes |
son equivalentes y el significado es el mismo que en la primera declaración |
CWG 1395 | C++14 | La deducción fallaba cuando A era de un paquete, y no había desempate con un paquete vacío |
Se permite la deducción, se añadió desempate |
CWG 1406 | C++11 | el tipo del nuevo primer parámetro añadido para una plantilla de función miembro no estática no era relevante para el calificador-referencia de esa plantilla |
el tipo es un tipo referencia rvalue si el calificador-referencia es &&
|
CWG 1446 | C++11 | el tipo del nuevo primer parámetro añadido para una plantilla de función miembro no estática sin calificador-referencia es un tipo de referencia lvalue, incluso si esa plantilla de función miembro se comparar con una plantilla de función cuyo primer parámetro tiene tipo referencia rvalue |
en este caso el tipo es un tipo referencia rvalue |
CWG 2373 | C++98 | Se añadían nuevos primeros parámetros a las listas de parámetros de plantillas de funciones de miembros estáticos en ordenación parcial |
no añadido |