El templating en C++ es un mecanismo que permite escribir código genérico, es decir, que puede trabajar con diferentes tipos de datos sin tener que reescribirlo para cada uno.
En lugar de definir una función o una clase específica para un tipo de dato (por ejemplo, int, double, string, etc.), definimos una versión genérica que funciona para distintos tipos.
Se llama así porque es como hacer “plantilla” para funciones o clases.
El templating no supone una pérdida de eficiencia, ya que el trabajo del templating ocurre durante la compilación, no durante la ejecución.
El compilador genera código específico para cada tipo que utilizas con el template (este proceso se llama instanciación de templates), generando versiones personalizadas del código para los tipos que necesite.
Templates de Función
Los templates de función permiten definir funciones genéricas que pueden trabajar con diferentes tipos de datos.
La sintaxis básica de un template de función es:
template <typename T>
T funcionGenerica(T a, T b) {
// Código de la función
}
En lugar de T
podríais usar cualquier otro nombre, pero en muchas ocasiones se usa este nombre
Vamos a verlo con un ejemplo
#include <iostream>
template <typename T>
T sumar(T a, T b) {
return a + b;
}
int main() {
std::cout << "Sumar enteros: " << sumar(3, 4) << std::endl;
std::cout << "Sumar flotantes: " << sumar(3.5, 4.5) << std::endl;
return 0;
}
En este ejemplo, la función sumar
puede trabajar tanto con enteros como con flotantes, porque la hemos definido como un template.
Internamente, el compilador genera una función para int
y otra para double
, lo que asegura que cada una sea igual de eficiente que si las hubieras escrito manualmente.
Templates de clase
Además de funciones, también es posible usar templates de clase, que nos permiten definir clases genéricas que pueden manejar diferentes tipos de datos.
La sintaxis básica de un template de clase es muy similar,
template <typename T>
class ClaseGenerica {
private:
T dato;
public:
ClaseGenerica(T dato) : dato(dato) {}
T getDato() { return dato; }
};
Veámoslo con un ejemplo,
#include <iostream>
template <typename T>
class Caja {
private:
T contenido;
public:
Caja(T contenido) : contenido(contenido) {}
T obtenerContenido() { return contenido; }
};
int main() {
Caja<int> cajaEntera(123);
Caja<std::string> cajaString("Hola, C++");
std::cout << "Contenido de cajaEntera: " << cajaEntera.obtenerContenido() << std::endl;
std::cout << "Contenido de cajaString: " << cajaString.obtenerContenido() << std::endl;
return 0;
}
En este ejemplo, la clase Caja
puede contener tanto enteros como cadenas de texto, o cualquier otra cosa (como buena caja que es).
Templates de plantillas de múltiples parámetros
También es posible definir templates que acepten más de un parámetro de tipo.
template <typename T, typename U>
class Par {
private:
T primero;
U segundo;
public:
Par(T primero, U segundo) : primero(primero), segundo(segundo) {}
T getPrimero() { return primero; }
U getSegundo() { return segundo; }
};
int main() {
Par<int, std::string> par(1, "Uno");
std::cout << "Primero: " << par.getPrimero() << ", Segundo: " << par.getSegundo() << std::endl;
return 0;
}
Especialización de Templates
La especialización de templates permite definir implementaciones específicas para ciertos tipos de datos.
#include <iostream>
template <typename T>
class Impresora {
public:
void imprimir(T dato) {
std::cout << "Dato: " << dato << std::endl;
}
};
// Especialización para tipo char*
template <>
class Impresora<char*> {
public:
void imprimir(char* dato) {
std::cout << "Cadena: " << dato << std::endl;
}
};
int main() {
Impresora<int> impresoraEntero;
impresoraEntero.imprimir(42);
Impresora<char*> impresoraCadena;
impresoraCadena.imprimir("Hola, especialización!");
return 0;
}
En este ejemplo, se especializa la clase Impresora
para el tipo char*
, permitiendo una implementación específica para cadenas de texto.
Deducción de Templates
La deducción de tipo en templates es el proceso mediante el cual el compilador de C++ determina automáticamente el tipo de los parámetros de un template basado en los argumentos que le pasamos.
template <typename T>
T suma(T a, T b) {
return a + b;
}
int main() {
int x = 5, y = 10;
double p = 2.5, q = 3.5;
// El compilador deduce el tipo de T automáticamente
std::cout << suma(x, y) << std::endl; // Deducido: T = int
std::cout << suma(p, q) << std::endl; // Deducido: T = double
}
En este ejemplo:
- Cuando llamas a
suma(x, y)
, el compilador ve quex
yy
son de tipoint
, así que deduce queT = int
. - Cuando llamas a
suma(p, q)
, deduce queT = double
.
Esto elimina la necesidad de escribir explícitamente suma<int>
o suma<double>
.
Deduction guides
Desde C++17, también podemos “ayudar” al compilador a deducir los tipos para templates de clase usando guías de deducción.
template <typename T>
class Caja {
public:
T contenido;
Caja(T c) : contenido(c) {}
};
// Deduction guide
Caja(const char*) -> Caja<std::string>;
int main() {
Caja c1(42); // Deducido: T = int
Caja c2("Hola"); // Deducido: T = std::string (gracias a la guía de deducción)
}
Esto es útil cuando el constructor de una clase no siempre indica claramente el tipo del template.