Una plantilla de función (function template) no es una función en sí misma, sino un modelo o plano que le indica al compilador cómo generar una función real para un tipo de dato específico. A diferencia de la sobrecarga convencional, donde escribes múltiples versiones de una función para distintos tipos, con las plantillas defines la lógica una sola vez. El compilador utiliza esta definición para realizar la instanciación, que es el proceso de generar código máquina especializado para los tipos que utilices. Esto ocurre porque C++ es un lenguaje de tipado estático y el compilador necesita conocer el tamaño y la alineación exacta de los tipos en memoria para generar instrucciones eficientes.
Utilizarás plantillas siempre que la lógica de una función sea independiente del tipo de dato, como en algoritmos de comparación, intercambio de valores o sumas genéricas. El proceso es automático: mediante la deducción de argumentos, el compilador analiza los valores que pasas a la función y “adivina” qué tipo T debe usar. Sin embargo, si intentas pasar tipos incompatibles (como un int y un double) en una plantilla que espera un único tipo T, la deducción fallará por ambigüedad. Para evitar esto, puedes usar la especificación explícita, obligando al compilador a usar un tipo concreto. También existe la instanciación explícita, donde tú le ordenas al compilador que genere una versión para un tipo específico, incluso si no se usa en ese archivo, lo cual es útil para controlar la exposición de código o reducir tiempos de compilación. Si intentas ocultar el cuerpo de una plantilla en un archivo .cpp separado sin usar instanciación explícita, el enlazador (linker) fallará al no encontrar la definición necesaria en las otras unidades de traducción donde la uses.
#include <iostream>
#include <string>
// Usamos 'typename' para definir el parámetro de tipo.
// 'class' es equivalente en este contexto; son intercambiables.
template <typename T>
T mi_max(T a, T b) {
return (a > b) ? a : b;
}
// Una plantilla que acepta dos tipos distintos.
// El tipo de retorno se deduce mediante 'auto' (C++14).
template <typename T1, typename T2>
auto comparar_mezclados(T1 a, T2 b) {
return (a > b) ? a : b;
}
// Declaración de una función template.
// Solo la firma es visible aquí.
template <typename T>
T duplicar_valor(T valor);
// Instanciación explícita: Forzamos al compilador a generar
// el código para duplicar un 'int', incluso si no se usa en este archivo.
template int duplicar_valor<int>(int);
// Implementación de la función declarada arriba.
template <typename T>
T duplicar_valor(T valor) {
return valor * 2;
}
int main() {
// 1. Instanciación implícita por deducción de tipos.
// El compilador genera: mi_max<int>(int, int)
int i1 = 10, i2 = 20;
std::cout << "Max int: " << mi_max(i1, i2) << "\n";
// 2. El problema de la ambigüedad en la deducción.
// mi_max(3, 5.5) fallaría porque T no puede ser int y double a la vez.
// Resolvemos con especificación explícita de tipos:
double d = mi_max<double>(3, 5.5);
std::cout << "Max mixto (especificado): " << d << "\n";
// 3. Deducción de tipos con tipos diferentes.
// Aquí T1 es int y T2 es double.
auto res = comparar_mezclados(10, 5.5);
std::cout << "Comparar mixto (resultado auto): " << res << "\n";
// 4. Uso de la función con instanciación explícita previa.
std::cout << "Duplicar (int): " << duplicar_valor(21) << "\n";
return 0;
}
Análisis del código
Cuando llamas a mi_max(i1, i2), el compilador ve que ambos argumentos son int. Aplica las reglas de deducción de argumentos de template, identifica que T debe ser int y genera en el objeto máquina una función mi_max que opera específicamente con enteros. Esto no es polimorfismo en tiempo de ejecución (como las funciones virtuales), es polimorfismo en tiempo de compilación.
En el caso de mi_max<double>(3, 5.5), estamos realizando una especificación explícita de tipos. Al poner <double> después del nombre de la función, le indicamos al compilador que ignore la deducción automática y trate a ambos argumentos como double. Esto es crucial cuando la deducción es ambigua o cuando queremos forzar una conversión implícita de un argumento para que coincida con el tipo especificado.
La función comparar_mezclados<T1, T2> maneja dos parámetros de tipo distintos. Gracias a auto (introducido en [C++14]), el compilador puede determinar el tipo de retorno basándose en el resultado de la expresión (a > b) ? a : b. Si a es int y b es double, el resultado será double para mantener la precisión, y auto capturará ese tipo.
Finalmente, duplicar_valor ilustra el problema de las unidades de traducción. Aunque la definición está en el mismo archivo para que este ejemplo compile, en un proyecto real, si la definición de duplicar_valor estuviera en un .cpp y solo la declaración en un .h, el linker daría un error de “símbolo no encontrado” al intentar usarla en main.cpp. La instanciación explícita (template int duplicar_valor<int>(int);) soluciona esto al obligar al compilador a compilar la versión de int y guardarla en el objeto, permitiendo que el linker la encuentre después.
El error frecuente
Un error clásico que confunde a muchos desarrolladores es intentar compilar un proyecto donde las plantillas se definen en archivos .cpp de la siguiente manera:
// math_utils.cpp
template <typename T>
T sumar(T a, T b) {
return a + b;
}
// main.cpp
template <typename T>
T sumar(T a, T b); // Solo la declaración
int main() {
int x = sumar(5, 10); // ERROR: Error de enlazado (Linker Error)
return 0;
}
El compilador, al procesar main.cpp, ve la declaración de sumar pero no su cuerpo. Intenta realizar la instanciación para int en ese momento, pero no puede porque no tiene el código. El compilador asume que la definición estará en otro lugar, pero al llegar al enlazador, la función sumar<int>(int, int) nunca fue generada en ningún archivo objeto. Para solucionar esto, las plantillas deben residir en sus archivos de cabecera (.h o .hpp) o utilizar la técnica de la instanciación explícita mencionada anteriormente.
N° 48