Desde la llegada del estándar C++11, las plantillas variádicas han permitido a los desarrolladores escribir código más flexible al aceptar un número indeterminado de argumentos de tipos distintos. Esta funcionalidad es la base de herramientas estándar como std::tuple y los métodos emplace en contenedores STL.
Conceptos Fundamentales
Una plantillla variádica se define utilizando puntos suspensivos (...), los cuales cumplen dos funciones principales dependiendo de su ubicación:
- typename... Args: Define un "paquete de parámetros de plantilla" (template parameter pack).
- Args... args: Define un "paquete de parámetros de función" (function parameter pack).
template <typename... Tipos>
void prototipoFuncion(Tipos... argumentos);
Para conocer cuántos elementos contiene un paquete, se utiliza el operador sizeof...:
template <typename... T>
void contarElementos(T... args) {
std::cout << "Cantidad de argumentos: " << sizeof...(args) << std::endl;
}
Expansión de Paquetes en Funciones
A diferencia de los parámetros normales, no se puede acceder a un elemento del paquete mediante un índice. Es nceesario "expandirlo".
1. Expansión mediante Recursividad
Este método requiere una función base (caso de parada) y una función recursiva que procesa el primer elemento y delega el resto al siguiente nivel de recursión.
// Caso base: se llama cuando no hay más argumentos
void procesar() {
std::cout << "Fin de la lista." << std::endl;
}
// Función recursiva
template <typename T, typename... Resto>
void procesar(T cabeza, Resto... cola) {
std::cout << "Procesando: " << cabeza << std::endl;
procesar(cola...); // Llamada recursiva con el resto del paquete
}
2. Expansión con Listas de Inicialización y Comas
Para evitar la recursividad, se puede utilizar un std::initializer_list junto con el operador coma. Esto permite ejecutar una operación para cada elemento dentro de un contexto de construcción de arreglo.
template <typename T>
int ejecutarAccion(const T& dato) {
std::cout << dato << " ";
return 0;
}
template <typename... Args>
void desplegarParametros(Args... args) {
// Se expande la expresión para cada argumento
int dummy[] = { (ejecutarAccion(args), 0)... };
(void)dummy; // Evitar advertencia de variable no usada
std::cout << std::endl;
}
Plantillas Variádicas en Clases
Las clases también pueden ser variádicas. La técnica común para extraer los tipos es la especialización parcial o la herencia recursiva.
Especialización Parcial
// Declaración primaria
template <typename... Tipos>
class Almacen;
// Especialización para el caso base (vacío)
template <>
class Almacen<> {};
// Especialización recursiva
template <typename T, typename... Resto>
class Almacen<T, Resto...> : private Almacen<Resto...> {
public:
Almacen(T valor, Resto... demas) : Almacen<Resto...>(demas...), data(valor) {
std::cout << "Guardando: " << data << std::endl;
}
private:
T data;
};
Expresiones de Plegado (C++17)
Introducidas en C++17, las fold expresssions simplifican drásticamente la sintaxis necesaria para operar sobre paquetes de parámetros sin recurrir a funciones auxiliares o recursividad manual.
Existen cuatro variantes principales basadas en la dirección del plegado y si incluyen un valor inicial:
- Unario derecho:
(E op ...) - Unario izquierdo:
(... op E) - Binario derecho:
(E op ... op I) - Binario izquierdo:
(I op ... op E)
Ejemplo de impresión simplificada utilizando el operador de flujo <<:
template <typename... Args>
void imprimirTodo(Args... args) {
// Plegado binario izquierdo con el objeto cout
(std::cout << ... << args) << std::endl;
}
// Uso: imprimirTodo(10, " es mayor que ", 5.5);
Este mecanismo soporta la mayoría de los operadores binarios (+, *, &&, ||, etc.), permitiendo realizar cálculos como sumas de todos los parámetros de forma compacta: return (args + ...);.