Implementación y Expansión de Plantillas Variádicas en C++

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:

  1. typename... Args: Define un "paquete de parámetros de plantilla" (template parameter pack).
  2. 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 + ...);.

Etiquetas: cpp templates metaprogramming cpp17

Publicado el 7-2 21:34