Plantillas en C++
Holas, hoy voy a contarles un poco sobre las plantillas (templates) en C++, para que nos sirven y que podemos hacer con ellas.
Las plantillas son funciones especiales que trabajan con tipos genéricos de datos, lo cual nos permite desarrollar funciones, clases y estructuras que puedan recibir diferentes tipos de datos dependendiendo de la necesidad que se tenga al desarrollar una aplicación. Esto nos va a permitir ahorrar mucho código.
Las plantillas nos sirven para hacer programación genérica en C++
Plantilla Básica
Vamos a ver algunos ejemplos de cómo utilizar plantillas en C++.
Para nuestro primer ejemplo vamos a suponer que queremos averiguar qué número es mayor entre dos de ellos.
#include <iostream>
using namespace std;
int Max (int x, int y) {
return x > y ? x : y;
}
int main() {
cout << "El numero mayor es: " << Max(5, 3) << endl;
}
Este sencillo ejemplo nos va a retornar el mayor entre dos números enteros, pero que pasa si queremos también averiguar cual es mayor entre dos numeros decimales?
La solución puede ser hacer otra función que se llame Max (sobrecarga de funciones) que reciba float en vez de int, pero esto no es lo óptimo, puesto que estaríamos repitiendo el mismo código para poder recibir un tipo de dato diferente, para solucionar este inconveniente, le metemos mano al código y usamos plantillas
#include <iostream>
using namespace std;
template<typename T>
T Max (T x, T y) {
return x > y ? x : y;
}
int main() {
cout << "El numero mayor es: " << Max(5, 3) << endl;
cout << "El numero mayor es: " << Max(5.4f, 6.13f) << endl;
}
Como se puede observar ha sido utilizada la misma función para hacer la búsqueda del mayor número con dos tipos de datos diferentes.
Ahora vamos a explicar el código escrito en el bloque superior.
- Encima de la función Max está escrito
template<typename T>
lo cual nos dice que la funcion debajo de esa línea esta escrita con una plantilla. - Lo siguiente es utilizar T como tipo de dato, tanto para el tipo de retorno como para los tipos de datos que puede recibir la función.
Una ventaja de usar plantillas, es que las funciones creadas sólo serán instanciadas y compiladas cuando sean necesarias, es decir:
Se creará una función Max de tipo int si la llamamos con tipos enteros.
Si no llamamos nunca a la función, el compilador no la creará, esto puede hacer nuestro binario más pequeño.
Instanciación de plantillas
Existen algunas formas de hacer que nuestra plantilla sea incluída en el binario compilado, que son las siguientes:
- Instanciación
- Instanciación explícita
- Especialización explícita
La primera forma ya fue explicada (Cuando llamamos a la función), vamos entonces a explicar las otras formas de llamar a dicha plantilla.
#include <iostream>
#include <string.h>
using namespace std;
template<typename T>
T Max (T x, T y) {
return x > y ? x : y;
}
// Instanciación explícita
template float Max(float x , float y);
// Especialización explícita
// En esta especialización vamos a averiguar que
// letra es 'mayor' según el orden alfabético
template<> const char *Max<const char *>(const char *x, const char *y) {
return strcmp(x, y) > 0 ? x : y;
}
int main() {
cout << "El numero mayor es: " << Max(5, 3) << endl;
cout << "El numero mayor es: " << Max(5.4f, 6.13f) << endl;
const char *a{"A"};
const char *b{"B"};
auto s = Max(a, b);
cout << "La letra 'mayor' es: " << s << endl;
}
Para una Instaciación explícita se debe especificar a la plantilla con qué tipo de dato queremos utilizarla. Con respecto a la Especialización explícita hay algunas cosas a tomar en cuenta.
template<>
no usa tipo de dato comotypename T
tiene que ir vacía- Especificamos el tipo de dato a retornar en el nombre de la función
const char *Max<cons char *>
- Especificamos el tipo de dato de los argumentos
Plantilla Variada
Las plantillas "variadas" nos ayudan a aceptar argumentos sin especificar, esto puede ser n agumentos de n tipos de datos, lo cual puede ser muy útil en algunos casos, como cuando queremos recibir muchos parametros al momento de iniciar nuestra aplicación, vamos verlo con un ejemplo.
#include <iostream>
using namespace std;
void variadicPrint() {
cout << endl;
}
template<typename T, typename...Params>
// Pasamos las referencias de cada argumento
// así como los argumentos dinámicos
void variadicPrint(T &&eachArgument, Params&&... args) {
// Imprimimos el valor correspondiente
cout << eachArgument << " ";
// Pasamos los argumentos de manera adecuada para la impresión
variadicPrint(forward<Params>(args)...);
}
int main() {
variadicPrint(1, 2.5, "hola", 4, 3.141592);
return 0;
}
Plantilla en Estructura
Las plantillas tanmbién pueden ser utilizadas en una estructura, lo cual se realizará de la siguiete forma
#include <iostream>
using namespace std;
template<typename T>
struct Tipo {
T value;
};
int main() {
Tipo<int> tipoInt;
Tipo<float> tipoFloat;
Tipo<char> tipoChar;
tipoInt.value = 1;
tipoFloat.value = 3.45f;
tipoChar.value = 'A';
cout << "El valor de 'Int' es: " << tipoInt.value << endl;
cout << "El valor de 'Float' es: " << tipoFloat.value << endl;
cout << "El valor de 'Char' es: " << tipoChar.value << endl;
return 0;
}
De esta manera nuestra estructura Tipo puede ser llamada con un tipo de dato entero, flotante, char, etc
Plantilla en Clase
Por último pero no menos importante las plantillas también pueden ser aplicadas a clases. En el siguiente ejemplo explicaré como utilizar una plantilla en una clase.
#include <iostream>
using namespace std;
template<typename T>
// Creamos una clase para un Stack
class Stack {
T mBuffer[512];
int mTop{ -1 };
public:
// Función para añadir mas elementos al Stack
void push(const T &elem) {
mBuffer[++mTop] = elem;
}
// Función para quitar elementos del Stack
void pop();
// // Función que permite obtener el último elemento del Stack
const T& getTop() const {
return mBuffer[mTop];
}
// Función para saber si el Stack está vacío
bool isEmpty() {
return mTop == -1;
}
};
// Para definir una función de la clase
// fuera del "scope" de la misma y
// utilizando una plantilla se hace de
// siguiente manera
template<typename T>
void Stack<T>::pop() {
--mTop;
}
int main() {
// Vamos a crear una nueva instancia
// de nuestra clase con tipo de dato float
Stack<float> s;
// Agregamos datos a la instancia del Stack
s.push(1);
s.push(9);
s.push(0);
s.push(5);
s.push(4.12);
// Recorremos nuestro Stack
// imprimimos el útlimo elemento
// y lo borramos
while (!s.isEmpty()) {
cout << s.getTop() << " ";
s.pop();
}
cout << endl;
return 0;
}
Como hemos podido ver, las plantillas nos ayudan mucho en el momento de optimizar el código escrito y hacerlo más genérico.
El código de los ejemplos expuestos en este post, lo pueden encontrar en: https://github.com/nano-bytes/cpp-compilation/tree/master/templates
Happy hacking!
Hasta otra!