Generics en C++: para qué sirven y cómo usarlos
\n\nTodo programador, tarde o temprano, se enfrenta a la situación de tener que escribir una función que funcione con diferentes tipos de datos. Por ejemplo, una función para encontrar el elemento máximo — debería funcionar igual de bien con int, double y string. Sin herramientas especiales, habría que escribir una implementación separada para cada tipo. Aquí es donde los generics (tipos genéricos) o, como se llaman en C++, las plantillas, vienen al rescate.
En C++, el mecanismo de plantillas es la base de la programación genérica. Permite escribir código que funciona con cualquier tipo de datos sin sacrificar el rendimiento. En este artículo, analizaremos qué son los generics en C++, para qué sirven y cómo usarlos en la práctica. El material está dirigido a principiantes, por lo que iremos de lo simple a lo complejo.
\n\n¿Qué son las plantillas (generics) en C++?
\n\nUna plantilla (template) es una construcción que permite definir una función o clase que trabaja con un tipo de datos arbitrario. En lugar de un tipo concreto, se especifica un parámetro de tipo (por ejemplo, T), y el compilador, durante la compilación, sustituye el tipo necesario. Esto se llama instanciación de plantilla.
Imagina un molde para galletas. El molde es uno solo, pero las galletas se pueden hacer con diferentes masas: de mantequilla, de chocolate, de avena. La plantilla es el molde, y el tipo (int, double, string) es la masa. Describes el algoritmo una vez, y el compilador crea automáticamente versiones para los tipos necesarios.
\n\nHay dos tipos de plantillas:
\n- \n
- Plantillas de funciones — para crear funciones genéricas. \n
- Plantillas de clases — para crear clases genéricas (por ejemplo, contenedores). \n
Plantillas de funciones: el primer paso hacia el código genérico
\n\nEmpecemos con lo más simple: una función plantilla. Supongamos que necesitamos una función que devuelva el máximo de dos valores. Así se vería sin plantillas:
\n\nint max_int(int a, int b) {\n return (a > b) ? a : b;\n}\n\ndouble max_double(double a, double b) {\n return (a > b) ? a : b;\n}\n\nEl código se duplica. Con una plantilla, esto se resuelve de manera elegante:
\n\n#include <iostream>\n#include <string>\n\ntemplate <typename T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nint main() {\n std::cout << max_value(10, 20) << std::endl; // 20\n std::cout << max_value(3.14, 2.71) << std::endl; // 3.14\n std::cout << max_value(std::string("apple"), \n std::string("banana")) << std::endl; // banana\n return 0;\n}\n\nLa palabra clave template le indica al compilador que esto es una plantilla. <typename T> es la lista de parámetros de la plantilla. T es un nombre convencional para el tipo que se sustituirá más adelante. En lugar de typename se puede usar class — no hay diferencia:
template <class T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nCuando llamas a max_value(10, 20), el compilador ve que los argumentos son int y crea automáticamente una versión de la función para int. Este proceso se llama deducción de tipos. Si es necesario, se puede especificar el tipo explícitamente:
std::cout << max_value<double>(5, 7.5) << std::endl; // 7.5\n\nImportante: la función plantilla debe definirse en un archivo de encabezado (o antes de su uso), ya que el compilador necesita ver la definición completa para la instanciación.
\n\nPlantillas de clases: creando contenedores universales
\n\nLas plantillas de clases son una herramienta aún más potente. El ejemplo más claro son los contenedores de la biblioteca estándar (STL): std::vector<int>, st