Articulo de referencia

Plantilla (C++)

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 la declaración de una...

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 la declaración de una función o clase haga referencia, mediante una variable genérica, a otra clase diferente (ya sea un tipo de dato integrado o uno recién declarado ) sin necesidad de crear una declaración completa para cada una de estas clases.

En términos sencillos, una clase o función con plantilla sería el equivalente a (antes de compilar) copiar y pegar el bloque de código con plantilla donde se utiliza, y luego reemplazar el parámetro de la plantilla por el real. Por esta razón, las clases que emplean métodos con plantilla colocan la implementación en los archivos de cabecera ( *.h ), ya que ningún símbolo podría compilarse sin conocer su tipo de antemano.

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 el lenguaje CLU y los genéricos proporcionados por Ada . [ 1 ]

Descripción general técnica

Existen tres tipos de plantillas: plantillas de función , plantillas de clase y, desde C++14 , plantillas de variables . Desde C++11 , las plantillas pueden ser variádicas o no variádicas; en versiones anteriores de C++ siempre eran no variádicas.

Las plantillas de C++ son Turing completas . [ 2 ]

Plantillas de funciones

Una plantilla de función se comporta como una función, excepto que puede aceptar argumentos de varios tipos, lo que permite un comportamiento genérico de tipos (ver ejemplo). En otras palabras, una plantilla de función representa una familia de funciones. El formato para declarar plantillas de función con parámetros de tipo es:

plantilla < class Identifier > Declaración ; plantilla < typename Identifier > Declaración ;

Ambas expresiones tienen el mismo significado y se comportan exactamente igual. La segunda forma se introdujo para evitar confusiones, [ 3 ] ya que un parámetro de tipo no tiene por qué ser una clase hasta C++20. (Puede ser un tipo básico como into double).

Por ejemplo, la biblioteca estándar de C++ contiene la plantilla de función max(x, y)que devuelve el mayor de xy y. Esa plantilla de función podría definirse de esta manera [ 4 ] :

plantilla < typename T > [[ nodiscard ]] constexpr T & max ( const T & a , const T & b ) noexcept { return a < b ? b : a ; }

Esta única definición de función funciona con muchos tipos de datos. En concreto, funciona con todos los tipos de datos para los que está definido el operador < (menor que) y devuelve un valor con un tipo convertible a bool. 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 única descripción de función y facilitar la lectura del código.

Una plantilla de función instanciada generalmente produce el mismo código objeto, en comparación con escribir funciones separadas para todos los diferentes tipos de datos utilizados en un programa específico. Por ejemplo, si un programa utiliza una versión inty una doubleversión de la max()plantilla de función anterior, el compilador creará una versión de código objeto de max()que opera sobre intargumentos y otra versión de código objeto que opera sobre doubleargumentos. La salida del compilador será idéntica a la que se habría producido si el código fuente hubiera contenido dos versiones separadas no plantilladas de , una escrita para manejar y otra escrita para manejar .max()intdouble

Así es como se podría utilizar la plantilla de función:

importar std ;int main () { // Esto llamará a max<int> por deducción implícita de argumentos. std :: println ( "{}" , std :: max ( 3 , 7 ));// Esto llamará a max<double> mediante deducción implícita de argumentos. std :: println ( "{}" , std :: max ( 3.0 , 7.0 ));// Necesitamos especificar explícitamente el tipo de los argumentos; // aunque std::type_identity podría resolver este problema... std :: println ( "{}" , max <double> ( 3 , 7.0 ) ); }

En los dos primeros casos, Tel compilador deduce automáticamente el argumento de la plantilla como inty double, respectivamente. En el tercer caso, la deducción automática de max(3, 7.0)fallaría porque el tipo de los parámetros debe coincidir exactamente con los argumentos de la plantilla. Por lo tanto, instanciamos explícitamente la doubleversión con max<double>().

Esta plantilla de función puede instanciarse con cualquier tipo copiable para el cual la expresión sea válida. Para tipos definidos por el usuario, esto implica que el operador menor que ( ) debe estar sobrecargado en el tipo.y < x<

Plantillas de funciones abreviadas

Desde C++20 , al usar auto`or` concept autoen cualquiera de los parámetros de una declaración de función , dicha declaración se convierte en una declaración de plantilla de función abreviada . [ 5 ] Dicha declaración declara una plantilla de función y se agrega un parámetro de plantilla inventado para cada marcador de posición a la lista de parámetros de plantilla:

// equivalente a: // plantilla <typename T> // void f1(T x); void f1 ( auto x );// equivalente a (si Concept1 es un concepto): // <Concept1 T> // void f2(T x); void f2 ( Concept1 auto x );// equivalente a (si Concept2 es un concepto): // plantilla <Concept2... Ts> // void f3(Ts... xs) void f3 ( Concept2 auto ... xs );// equivalente a (si Concept2 es un concepto): // plantilla <Concept2 T> // void f4(T... xs); void f4 ( Concept2 auto xs , ...);// equivalente a (si Concept3 y Concept4 son conceptos): // plantilla <Concept3 T, Concept4 U> // f5(const T* t, U& u); void f5 ( const Concept3 auto * t , Concept4 auto & u );

Restringir el max()uso de conceptos podría verse algo así:

usando std :: totalmente_ordenado ;// en la declaración del nombre del tipo: plantilla < totally_ordered T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }// en la cláusula requires: template < typename T > requires totally_ordered < T > [[ nodiscard ]] constexpr T max ( T x , T y ) noexcept { return x < y ? y : x ; }

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 . Una plantilla de clase se instancia pasándole un conjunto dado de tipos como argumentos de plantilla. [ 6 ] La biblioteca estándar de C++ contiene muchas plantillas de clase, 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 }; // (Casi) de std::numbers::pi

Parámetros de plantilla que no son de tipo

Aunque la creación de plantillas en tipos, como en los ejemplos anteriores, es la forma más común de crear plantillas en C++, también es posible crear plantillas en valores. Así, por ejemplo, una clase declarada con

plantilla < int K > clase MyClass ;

puede instanciarse con un específico int.

Como ejemplo práctico, el tipo de matriz de tamaño fijo de la biblioteca estándar se basa en una plantilla que incluye un tipo (que representa el tipo de objeto que contiene la matriz) y un número de tipo (que representa la cantidad de elementos que contiene la matriz). Para crear una clase equivalente a , se puede declarar de la siguiente manera:std::arraystd::size_tArraystd::array

plantilla < clase T , tamaño_t N > estructura Array ;

chary se podría declarar una matriz de seis s:

Matriz < carácter , 6 > miMatriz ;

Especialización de plantillas

Cuando se instancia 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 a esa especialización se la denomina especialización generada.

Especialización explícita de plantillas

En ocasiones, el programador puede optar por implementar una versión especial de una función (o clase) para un conjunto determinado de argumentos de tipo plantilla, lo que se conoce como especialización explícita. De esta forma, ciertos tipos de plantilla pueden tener una implementación especializada optimizada para el tipo o una implementación más significativa que la genérica.

  • Si una plantilla de clase se especializa mediante un subconjunto de sus parámetros, se denomina especialización parcial de plantilla (las plantillas de función no se pueden especializar parcialmente).
  • Si todos los parámetros están especializados, se trata de una especialización completa .

La especialización explícita se utiliza cuando el comportamiento de una función o clase para determinadas elecciones 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 que se muestra a continuación define una implementación específica para max()argumentos de tipo const char*:

importar std ;template <> [[ nodiscard ]] constexpr const char * max ( const char * a , const char * b ) noexcept { // Normalmente, el resultado de una comparación directa // entre dos cadenas C es un comportamiento indefinido; // usar std::strcmp lo define. return std :: strcmp ( a , b ) > 0 ? a : b ; }

Plantillas variádicas

C++11 introdujo las plantillas variádicas , que pueden tomar un número variable de argumentos de una manera algo similar a las funciones variádicas como std::printf.

using std :: format_string ; using std :: ofstream ;clase enumeración Nivel { ... };ofstream logFile { "logfile.txt" };plantilla < typename ... Args > void log ( const format_string < Args ... >& fmt , Args && ... args ) { std :: println ( logFile , fmt , args ...); }

Dado que en C++ solo se admiten parámetros variádicos al estilo de C, la única forma de obtener funciones variádicas con seguridad de tipos (como en Java ) es mediante plantillas variádicas.

Alias ​​de plantilla

C++11 introdujo los alias de plantilla, que actúan como typedefs parametrizados .

El siguiente código muestra cómo renombrar std::mapy TreeMap, std::unordered_mapasí HashMapcomo cómo crear un alias StringHashMappara std::unordered_map<K, std::string>. Esto permite, por ejemplo, StringHashMap<int>que se utilice como abreviatura de std::unordered_map<int, std::string>.

usando String = std :: string ;// permitiendo la especialización opcional de funciones hash, asignadores, etc. plantilla < typename K , typename V , typename Compare = std :: less < K > , typename Alloc = std :: allocator < std :: pair < ​​const K , T >> > usando TreeMap = std :: map < K , V , Compare , Alloc > ;plantilla < typename K , typename V , typename HashFn = std :: hash < K > , typename KeyEq = std :: equal_to < K > , typename Alloc = std :: allocator < std :: pair < ​​const K , T >> > usando HashMap = std :: unordered_map < K , V , HashFn , KeyEq , Alloc > ;// o, permitiendo solo que K y V se especialicen: plantilla < typename K , typename V > usando TreeMap = std :: map < K , V > ;plantilla < typename K , typename V > usando HashMap = std :: unordered_map < K , V > ;// Definiendo StringHashMap<K> = HashMap<K, String> plantilla < typename K > usando StringHashMap = HashMap < K , String > ;StringHashMap < int > myMap = /* algo aquí... */ ;

Plantillas restringidas

Desde C++20 , las plantillas pueden restringirse de forma similar a los comodines genéricos en Java o C# y las cláusulas de Rustwhere . Esto se logra mediante conceptos que representan un conjunto de predicados booleanos que se evalúan en tiempo de compilación.

Por ejemplo, este código utiliza std::derived_from<Derived, Based>como límite superior de herencia. Una clase satisface este concepto si hereda de Player, y las clases que no lo hacen no pueden utilizarse como parámetro de plantilla en processListOfPlayers().

importar std ;usando std :: derivado_de ; usando std :: vector ;clase Jugador { // ... };// Se requiere que T sea un tipo cuyo límite superior de herencia sea Player, // bloqueando cualquier tipo que no herede de Player template < derived_from < Player > T > void processListOfPlayers ( const vector < T >& players ) { // ... }

Se pueden utilizar conceptos para implementar una forma de parámetros variádicos con seguridad de tipos. Por ejemplo, la firma de Java.<T>voidfn(T...args);

usando std :: same_as ;plantilla < typename T > void fn ( same_as < T > auto ... args ) { // ... }

Además, para restringirlos a un tipo específico, se pueden usar los conceptos y . Por ejemplo, las firmas de Java y podrían escribirse como y respectivamente.std::same_as<T,U>std::convertible_to<T,U>voidfoo(int...args);voidbar(String...args);voidfoo(same_as<int>auto...args);voidbar(convertible_to<string>auto...args);

Plantillas exportadas

En C++03 , se añadieron las "plantillas exportadas" a C++. [ 7 ] Posteriormente, se eliminaron en C++11 , debido a que muy pocos compiladores admitían esta característica. [ 8 ] El único compilador conocido que admitía plantillas exportadas era Comeau C/C++ . Entre las razones citadas para su eliminación se encontraban:

  • Costoso o complejo de implementar
  • Poco beneficio para la mayoría de los usuarios, así como poco interés.
  • Difícil de usar
  • Cambios en los significados de las características lingüísticas existentes.
  • Restringir el desarrollo futuro de C++

Una "plantilla exportada" es esencialmente una plantilla de clase cuyos miembros de datos estáticos y métodos no en línea se exportan. Debe estar marcada con la palabra clave export. Lo que distingue a una "plantilla exportada" es el hecho de que no necesita estar definida en una unidad de traducción que utilice la plantilla. [ 9 ] [ 10 ] Por ejemplo (en C++03):

Archivo1.cpp :

#include <iostream>static void trace () { std :: cout << "Archivo 1" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y );int main () { trace (); std :: cout << min ( 2 , 3 ) << std :: endl ; }

Archivo2.cpp :

#include <iostream>static void trace () { std :: cout << "Archivo 2" << std :: endl ; }export template < typename T > T min ( const T & x , const T & y ) { trace (); return a < b ? a : b ; }

Con la introducción de los módulos en C++20 , la palabra clave exportse volvió a añadir a C++. Esto permitió nuevamente declaraciones como esta:

importar std ;usando std :: is_base_of_v ;export class Atom { // ... };export template < typename T > concept ExtendsAtom = is_base_of_v < Atom , T > ;export template < ExtendsAtom Instance , typename ... Bases > class Cluster : public Bases ... { private : Instance x ; public : explicit Cluster ( Instance x , Bases && ... bases ) : Bases ( bases )..., x { x } {}// ... };

Las ventajas en la velocidad de compilación que se suponía que ofrecían las plantillas exportadas ya las ofrecen los módulos, lo que hace que esta función sea esencialmente obsoleta y haya sido reemplazada por los módulos.

Características de programación genéricas en otros lenguajes

Inicialmente, el concepto de plantillas no estaba incluido en algunos lenguajes, como Java y C# 1.0. La adopción de genéricos por parte de Java imita el comportamiento de las plantillas, pero es técnicamente diferente. C# incorporó genéricos (tipos parametrizados) en .NET 2.0. Los genéricos en 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++ . [ 11 ] Algunas de las características avanzadas de las plantillas utilizadas por bibliotecas como Boost y STLSoft , e implementaciones de la STL, para la metaprogramación de plantillas (especialización explícita o parcial, argumentos de plantilla predeterminados, argumentos de plantilla que no son de tipo, argumentos de plantilla de plantilla, ...) no están disponibles con los genéricos.

En las plantillas de C++, históricamente, las comprobaciones en tiempo de compilación se realizaban mediante la coincidencia de patrones sobre los argumentos de la plantilla. Por ejemplo, la clase base de la plantilla en el ejemplo Factorial que se muestra a continuación se implementa mediante la coincidencia de 0 en lugar de una prueba de desigualdad, que antes no estaba disponible. Sin embargo, la llegada en C++11 de características de la biblioteca estándar, como , std::conditionalha proporcionado una forma alternativa y más flexible de gestionar la instanciación condicional de plantillas.

// Plantilla de inducción < unsigned int N > struct Factorial { static constexpr unsigned int value = N * Factorial < N - 1 >:: value ; };// Caso base mediante especialización de plantilla: plantilla <> struct Factorial < 0 > { static constexpr unsigned int value = 1 ; };

Con estas definiciones, se puede calcular, por ejemplo, 6! en tiempo de compilación utilizando la expresión Factorial<6>::value.

Alternativamente, constexpren C++11 / if constexpren C++17 se pueden utilizar para calcular dichos valores directamente mediante una función en tiempo de compilación:

plantilla < entero sin signo N > [[ no descartar ]] constexpr entero sin signo factorial () noexcept { si constexpr ( N ​​<= 1 ) { retornar 1 ; } else { retornar N * factorial < N - 1 > (); } }

Por este motivo, la metaprogramación con plantillas se utiliza ahora principalmente para realizar operaciones sobre tipos.

Véase también

Referencias

  1. Stroustrup, Bjarne (8 de septiembre de 2004). "El lenguaje de programación C++" . Stroustrup.com (página web personal) (3.ª  edición especial).
  2. "Las plantillas de C++ son Turing completas" (PDF) . ubiietylab.net . Archivado del original (PDF) el 1 de noviembre de 2013.
  3. Lippman, Stan (11 de agosto de 2004). "Por qué C++ admite tanto Class como Typename para parámetros de tipo" . Microsoft Developers Network (MSDN) .
  4. "std::max" . cppreference.com . Consultado el 21 de julio de 2025 .
  5. "P1141R1 - Otro enfoque más para declaraciones restringidas" . Archivado del original el 11/11/2018 . Recuperado el 11/11/2018 .
  6. ^ Vandevoorde, Daveed; Josuttis, Nicolai (2002). Plantillas C++: la guía completa . Addison Wesley . ISBN 978-0-201-73484-3.
  7. "Plantilla de clase" . cppreference.com . 7 de octubre de 2025.
  8. Herb Sutter (3 de marzo de 2003). "Por qué no podemos permitirnos exportar" (PDF) . open-std.org . WG 21.
  9. "Descripción general de la exportación" . comeaucomputing.com . Comeau Computing. Archivado del original el 2 de junio de 2003.
  10. "Descripción general de la plantilla exportada" . comeaucomputing.com . Comeau Computing. Archivado del original el 22 de abril de 2003.
  11. "Diferencias entre plantillas de C++ y genéricos de C# (Guía de programación de C#)" . 12 de marzo de 2024.
  • Demostración de la completitud de Turing de las plantillas de C++ ( implementación del cálculo lambda )