Plantillas de parámetros variables en C++11

Concepto de plantillas de parámetros variables

Las plantillas de parámetros variables representan una de las adiciones más potentes en C++11. Permiten una generalización avanzada de los parámetros, posibilitando la creación de planitllas de función y plantillas de clase que aceptan un número variable de argumentos.

  • Antes de C++11, las plantillas de función y de clase solo podían definir un número fijo de parámetros. La introducción de los parámetros variables supuso una mejora significativa. Sin embargo, su uso requiere cierta técnica debido a su nivel de abstracción.
  • Con anterioridad ya existía conceptos de parámetros variables en funciones como printf, que admite un número arbitrario de argumentos. No obstante, esto se refiere a parámetros de función variables, no a parámetros de plantilla variables.

Sintaxis de definición

Una plantilla de parámetros variables se define de la siguiente manera:

template<typename... Tipos>
tipo_retorno nombre_funcion(Tipos... parametros)
{
    // Cuerpo de la función
}

Observaciones clave:

  • El operador de elipsis (...) antes del nombre Tipos indica que es un parámetro de plantilla variable (un paquete de parámetros). Este paquete puede contener de cero a N argumentos de plantilla. parametros es un paquete de parámetros de función.
  • Los nombres Tipos y parametros son arbitrarios; se pueden elegir otros identificadores.

Al llamar a una función así definida, se pueden pasar argumentos de diferentes tipos y en cualquier cantidad:

template<typename... Tipos>
void MiFuncion(Tipos... argumentos) {}

int main()
{
    MiFuncion();
    MiFuncion(42);
    MiFuncion(3.14, 'x');
    MiFuncion(true, 5, std::string("ejemplo"));
    return 0;
}

Para determinar el número de argumentos en el paquete, se puede utilizar el operador sizeof...:

template<typename... Tipos>
void MostrarCantidad(Tipos... argumentos)
{
    std::cout << "Número de parámetros: " << sizeof...(argumentos) << std::endl;
}

Métodos de expansión del paquete de parámetros

Expansión recursiva

Este método sigue un patrón estructurado:

  • Se añade un parámetro de plantilla adicional para extraer un argumento del paquete.
  • Dentro de la plantilla, se realiza una llamada recursiva pasando el resto del paquete.
  • La recursión continúa hasta que el paquete queda vacío.

Por ejemplo, para imprimir cada argumento individualmente:

// Caso base (termina la recursión)
void ProcesarArgumentos()
{
    std::cout << std::endl;
}

// Caso recursivo
template<typename Primero, typename... Resto>
void ProcesarArgumentos(Primero primer_param, Resto... resto_params)
{
    std::cout << primer_param << " ";
    ProcesarArgumentos(resto_params...);
}

Una alternativa más modular separa la interfaz pública de la lógica recursiva:

// Implementación recursiva interna
void ImprimirArgumentosInterno()
{
    std::cout << std::endl;
}

template<typename T, typename... Args>
void ImprimirArgumentosInterno(T actual, Args... siguientes)
{
    std::cout << actual;
    if constexpr (sizeof...(siguientes) > 0) {
        std::cout << ", ";
    }
    ImprimirArgumentosInterno(siguientes...);
}

// Función pública
template<typename... Args>
void ImprimirArgumentos(Args... args)
{
    ImprimirArgumentosInterno(args...);
}

Expansión mediante expresión con coma

Este método aprovecha las características de la expresión con coma en C++:

  • Las expresiones con coma se evalúan de izquierda a derecha, devolviendo el valor de la última expresión.
  • Se establece una expresión final (por ejemplo, un entero) para garantizar un tipo de retorno conocido.
  • La acción a realizar por cada parámetro se encapsula en una función auxiliar.

La expansión se logra inicializando una matriz con los resultados de evaluar la expresión con coma para cada parámetro:

// Función de procesamiento para un solo argumento
template<typename T>
void Transformar(const T& elemento)
{
    std::cout << "[" << elemento * 2 << "] "; // Ejemplo: doblar el valor
}

// Función principal de expansión
template<typename... Args>
void ProcesarConComa(Args... argumentos)
{
    // Lista de inicialización con expresión con coma
    int dummy[] = { (Transformar(argumentos), 0)... };
    std::cout << std::endl;
}

En esta construcción, (Transformar(argumentos), 0)... se expande en una lista de expresiones con coma. Primero se ejecuta Transformar(argumentos) para cada argumento y luego se evalúa 0, cuyo valor se usa para inicializar cada elemento de la matriz dummy. La elipsis fuera de la expresión con coma indica que se debe expandir todo el paquete de parámetros de esa manera.

Etiquetas: C++11 plantillas parámetros variables recursion expresión con coma

Publicado el 7-26 21:18