Las plantillas son una característica del lenguaje de programación C ++ que permite que las funciones y clases operen con tipos genéricos . Esto permite que una función o clase trabaje en muchos tipos de datos diferentes sin tener que reescribirlos para cada uno.
La biblioteca estándar de C ++ proporciona muchas funciones útiles dentro de un marco de plantillas conectadas.
Las principales inspiraciones para las plantillas de C ++ fueron los módulos parametrizados proporcionados por CLU y los genéricos proporcionados por Ada . [1]
Resumen técnico
Hay tres tipos de plantillas: plantillas de funciones , plantillas de clases y, desde C ++ 14 , plantillas de variables . Desde C ++ 11 , las plantillas pueden ser variadas o no variadas; en versiones anteriores de C ++ siempre son no variadas.
Plantillas de funciones
Una plantilla de función se comporta como una función excepto que la plantilla puede tener argumentos de muchos tipos diferentes (ver ejemplo). En otras palabras, una plantilla de función representa una familia de funciones. El formato para declarar plantillas de funciones con parámetros de tipo es:
plantilla < identificador de clase > function_declaration ; template < typename identifier > function_declaration ;
Ambas expresiones tienen el mismo significado y se comportan exactamente de la misma manera. La última forma se introdujo para evitar confusiones, [2] ya que un parámetro de tipo no necesita ser una clase. (También puede ser un tipo básico como int
o double
).
Por ejemplo, la biblioteca estándar de C ++ contiene la plantilla de función max(x, y)
que devuelve el mayor de x
y y
. Esa plantilla de función podría definirse así:
plantilla < typename T > en línea T max ( T a , T b ) { return a > b ? a : b ; }
Esta definición de función única funciona con muchos tipos de datos. Específicamente, funciona con todos los tipos de datos para los que se define > (el operador mayor que). El uso de una plantilla de función ahorra espacio en el archivo de código fuente, además de limitar los cambios a una descripción de función y facilitar la lectura del código.
Sin embargo, una plantilla no produce un código de objeto más pequeño en comparación con la escritura de funciones separadas para todos los diferentes tipos de datos utilizados en un programa específico. Por ejemplo, si un programa usa tanto int
una double
versión como una versión de la max()
plantilla de función que se muestra arriba, el compilador creará una versión de código objeto max()
que opera con int
argumentos y otra versión de código objeto que opera con double
argumentos. La salida del compilador será idéntica a la que se habría producido si el código fuente hubiera contenido dos versiones separadas sin plantilla de max()
, una escrita para manejar int
y otra escrita para manejar double
.
Así es como se puede usar la plantilla de función:
#include int main () { // Esto llamará a max por deducción implícita de argumentos. std :: cout << max ( 3 , 7 ) << std :: endl ; // Esto llamará a max por deducción implícita de argumentos. std :: cout << max ( 3.0 , 7.0 ) << std :: endl ; // Esto depende del compilador. Algunos compiladores manejan esto definiendo una // función de plantilla como double max (double a, double b) ;, mientras que en algunos compiladores // necesitamos convertirlo explícitamente, como std :: cout << max (3,7,0); std :: cout << max ( 3 , 7.0 ) << std :: endl ; std :: cout << max < doble > ( 3 , 7.0 ) << std :: endl ; return 0 ; }
En los dos primeros casos, T
el compilador deduce automáticamente que el argumento de plantilla es int
y double
, respectivamente. En el tercer caso, la deducción automática de max(3, 7.0)
fallaría porque el tipo de parámetros debe coincidir en general exactamente con los argumentos de la plantilla. Por lo tanto, instanciamos explícitamente la double
versión con max
.
Se puede crear una instancia de esta plantilla de función con cualquier tipo de construcción de copia para el que la expresión y > x
sea válida. Para los tipos definidos por el usuario, esto implica que el operador mayor que ( >
) debe estar sobrecargado en el tipo.
Plantillas de clase
Una plantilla de clase proporciona una especificación para generar clases basadas en parámetros. Las plantillas de clase se utilizan generalmente para implementar contenedores . Se crea una instancia de una plantilla de clase pasándole un conjunto determinado de tipos como argumentos de plantilla. [3] La biblioteca estándar de C ++ contiene muchas plantillas de clases, en particular los contenedores adaptados de la biblioteca de plantillas estándar , como vector
.
Plantillas variables
En C ++ 14, las plantillas también se pueden usar para variables, como en el siguiente ejemplo:
plantilla < typename T > constexpr T pi = T { 3.141592653589793238462643383L }; // Desde std :: numeros :: pi
Especialización en plantillas
Cuando se crea una instancia de una función o clase a partir de una plantilla, el compilador crea una especialización de esa plantilla para el conjunto de argumentos utilizados, y la especialización se denomina especialización generada.
Especialización explícita en plantillas
A veces, el programador puede decidir implementar una versión especial de una función (o clase) para un conjunto dado de argumentos de tipo de plantilla, lo que se denomina especialización explícita. De esta manera, ciertos tipos de plantillas pueden tener una implementación especializada que está optimizada para el tipo o una implementación más significativa que la implementación genérica.
- Si una plantilla de clase está especializada por un subconjunto de sus parámetros, se denomina especialización de plantilla parcial (las plantillas de función no pueden especializarse parcialmente).
- Si todos los parámetros son especializados, es una especialización completa .
La especialización explícita se utiliza cuando el comportamiento de una función o clase para elecciones particulares de los parámetros de la plantilla debe desviarse del comportamiento genérico: es decir, del código generado por la plantilla principal, o las plantillas. Por ejemplo, la definición de plantilla a continuación define una implementación específica de max()
para argumentos de tipo bool
:
plantilla <> bool max < bool > ( bool a , bool b ) { return a || b ; }
Plantillas variadic
C ++ 11 introdujo plantillas variadas , que pueden tomar un número variable de argumentos de una manera algo similar a las funciones variadas como std::printf
. Las plantillas de funciones, las plantillas de clases y (en C ++ 14) las plantillas de variables pueden ser variables.
Alias de plantilla
C ++ 11 introdujo los alias de plantilla, que actúan como typedefs parametrizados .
El siguiente código muestra la definición de un alias de plantilla StrMap
. Esto permite, por ejemplo, StrMap
usarse como abreviatura de std::unordered_map
.
plantilla < clase T > usando StrMap = std :: unordered_map < T , std :: string > ;
Funciones de programación genéricas en otros idiomas
Inicialmente, el concepto de plantillas no se incluyó en algunos lenguajes, como Java y C # 1.0. La adopción de genéricos de Java imita el comportamiento de las plantillas, pero es técnicamente diferente. C # agregó genéricos (tipos parametrizados) en .NET 2.0. Los genéricos de Ada son anteriores a las plantillas de C ++.
Aunque las plantillas de C ++, los genéricos de Java y los genéricos de .NET a menudo se consideran similares, los genéricos solo imitan el comportamiento básico de las plantillas de C ++. [4] Algunas de las funciones de plantilla avanzadas utilizadas por bibliotecas como Boost y STLSoft , e implementaciones de la propia STL, para la metaprogramación de plantillas (especialización explícita o parcial, argumentos de plantilla predeterminados, argumentos de plantilla sin tipo, argumentos de plantilla de plantilla, .. .) no están disponibles con genéricos.
En las plantillas de C ++, los casos en tiempo de compilación se realizaban históricamente mediante la coincidencia de patrones sobre los argumentos de la plantilla. Por ejemplo, la clase base de la plantilla en el ejemplo factorial a continuación se implementa haciendo coincidir 0 en lugar de con una prueba de desigualdad, que anteriormente no estaba disponible. Sin embargo, la llegada a C ++ 11 de características de biblioteca estándar como std :: conditional ha proporcionado otra forma más flexible de manejar la instanciación de plantilla condicional.
// Inducciónplantilla < unsigned N > struct Factorial { static const unsigned value = N * Factorial < N - 1 > :: value ; };// Caso base a través de la especialización de plantillas:plantilla <> struct Factorial < 0 > { valor sin signo constante estático = 1 ; };
Con estas definiciones, se puede calcular, ¡digamos 6! en tiempo de compilación usando la expresión Factorial<6>::value
. Alternativamente, constexpr en C ++ 11 se puede usar para calcular dichos valores directamente usando una función en tiempo de compilación.
Ver también
Referencias
- ↑ Stroustrup, Bjarne (8 de septiembre de 2004). "El lenguaje de programación C ++ (tercera edición y edición especial)" . Página de inicio de Bjarne Stroustrup .
- ^ Lippman, Stan. "Por qué C ++ admite tanto la clase como el nombre de tipo para los parámetros de tipo" . MSDN .
- ^ Vandevoorde, Daveed; Josuttis, Nicolai (2002). Plantillas C ++: la guía completa . Addison Wesley . ISBN 978-0-201-73484-3.
- ^ Diferencias entre plantillas de C ++ y genéricos de C # (Guía de programación de C #)
enlaces externos
- Demostración de la completitud de Turing de las plantillas de C ++ (implementación de cálculo Lambda)