Introducción: ¿Qué son los Generics (plantillas) en C++ y para qué sirven?
En el mundo de la programación en C++, uno de los conceptos clave que permite escribir código flexible, reutilizable y type-safe son las plantillas (templates). A menudo se les llama "generics" por analogía con otros lenguajes como Java o C#, aunque en C++ este concepto es significativamente más potente y profundo.
Imagina que necesitas escribir una función para encontrar el elemento máximo en un array. Para números enteros (int) sería una función, para números de punto flotante (double) otra, y para cadenas (std::string) una tercera. Sin plantillas, tendrías que duplicar código, lo que conduce a errores y complica el mantenimiento.
Los Generics (plantillas) resuelven este problema. Permiten escribir código que funciona con cualquier tipo de dato, sin sacrificar el rendimiento (a diferencia de las funciones virtuales o void*). Las plantillas son la base de la biblioteca estándar de C++ (STL): contenedores (std::vector, std::map), algoritmos (std::sort, std::find) y punteros inteligentes (std::shared_ptr) — todos están implementados con plantillas.
En este artículo, analizaremos qué son las plantillas en C++, cómo declararlas y usarlas correctamente, y veremos ejemplos prácticos.
Parte principal: Cómo usar Generics (plantillas) en C++
1. Plantillas de funciones (Function Templates)
La forma más sencilla de empezar con generics es crear una plantilla de función. En lugar de escribir varias versiones sobrecargadas, escribes una, parametrizada por tipo.
Sintaxis:
template <typename T>T max(T a, T b) { return (a > b) ? a : b;}La palabra clave template le indica al compilador que esto es una plantilla. <typename T> declara el parámetro de tipo T. Puedes usar class en lugar de typename — no hay diferencia.
Ejemplo de uso:
#include <iostream>#include <string>
template <typename T>T max(T a, T b) { return (a > b) ? a : b;}
int main() { std::cout << max(10, 20) << std::endl; // int: 20 std::cout << max(3.14, 2.71) << std::endl; // double: 3.14 std::cout << max(std::string("C++"), std::string("Python")) << std::endl; // string: Python return 0;}Importante: El compilador genera una versión separada de la función para cada tipo utilizado. Esto se llama instanciación de plantilla. Gracias a esto se logra el máximo rendimiento — no hay sobrecarga por llamadas virtuales.
Varios parámetros de tipo:
template <typename T1, typename T2>void printPair(T1 a, T2 b) { std::cout << "(" << a << ", " << b << ")" << std::endl;}// printPair(1, "hello"); // Imprime: (1, hello)2. Plantillas de clases (Class Templates)
Las plantillas de clases permiten crear estructuras de datos genéricas. El ejemplo clásico es std::vector<T>. Puedes crear tu propia clase plantilla, por ejemplo, una pila simple:
template <typename T>class Stack {private: T* data; size_t size; size_t capacity;public: Stack(size_t cap = 10) : size(0), capacity(cap) { data = new T[capacity]; } ~Stack() { delete[] data; }
void push(const T& value) { if (size < capacity) { data[size++] = value; } }
T pop() { if (size > 0) { return data[--size]; } throw std::out_of_range("Stack is empty"); }
bool isEmpty() const { return size == 0; }};```