Plantillas variádicas
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 67 de 104.
¿Qué pasa si necesitas una función que acepte cualquier número de argumentos de cualquier tipo? Las plantillas tradicionales requieren que especifiques exactamente cuántos parámetros de tipo necesitas. Las plantillas variádicas resuelven esto permitiendo que las plantillas acepten un número arbitrario de argumentos de plantilla.
La sintaxis utiliza ... (puntos suspensivos) para crear un paquete de parámetros:
template <typename... Args>
void printAll(Args... args) {
// Args es un paquete de parámetros de plantilla
// args es un paquete de parámetros de función
}Para procesar los argumentos, normalmente se utiliza la recursividad con un caso base. El paquete de parámetros se expande un argumento a la vez hasta que no queda ninguno:
// Caso base: no quedan argumentos
void print() {
std::cout << std::endl;
}
// Caso recursivo: procesar el primer argumento, recurrir con el resto
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Expandir los argumentos restantes
}
print(1, 3.14, "hello", 'x'); // Salida: 1 3.14 hello xTambién puedes usar expresiones de plegado (C++17) para operaciones más simples sin recursividad explícita:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Expresión de plegado: suma todos los argumentos
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Salida: 10Las plantillas variádicas impulsan muchas características de la biblioteca estándar como std::make_unique, std::tuple y std::function. Permiten funciones con seguridad de tipos que funcionan con cualquier combinación de tipos y cantidades de argumentos.
Desafío
FácilVamos a construir un sistema de registro flexible que utiliza plantillas variádicas para manejar mensajes con cualquier número de argumentos. Crearás utilidades que pueden concatenar valores, contar argumentos y mostrar una salida formateada, todo utilizando paquetes de parámetros (parameter packs).
Organizarás tu código en dos archivos:
Variadic.h: Define aquí tus funciones de plantilla variádica.Crea una función
printAllque imprima todos sus argumentos separados por espacios, seguidos de una nueva línea. Utiliza el enfoque recursivo con un caso base que imprima solo una nueva línea, y un caso recursivo que imprima el primer argumento, un espacio, y luego realice la recursión con los argumentos restantes.Crea una función
countArgsque devuelva el número de argumentos que se le pasan. Utilizasizeof...para obtener el tamaño del paquete de parámetros.Crea una función
sumque sume todos los argumentos numéricos y devuelva el resultado. Utiliza una expresión de plegado (fold expression) con el operador+.Crea una función
productque multiplique todos los argumentos numéricos y devuelva el resultado. Utiliza una expresión de plegado (fold expression) con el operador*.main.cpp: Lee cuatro entradas (cada una en una línea separada):- Un entero
- Un double
- Un string
- Un carácter
Demuestra tus plantillas variádicas mediante:
- Llamar a
printAllcon las cuatro entradas e imprimir el resultado - Imprimir el recuento de argumentos al llamar a
countArgscon el entero, el double y el carácter:Argument count: <count> - Calcular la suma del entero, el double y los valores 10 y 5, luego imprimir:
Sum: <result> - Calcular el producto del entero y los valores 2 y 3, luego imprimir:
Product: <result> - Llamar a
printAllsolo con el string (un solo argumento) - Llamar a
printAllsin argumentos (debería imprimir solo una nueva línea)
Por ejemplo, con las entradas 5, 2.5, Hello, y X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
Observa cómo printAll maneja cualquier número de argumentos de tipos mixtos, desde cuatro argumentos hasta cero. Las funciones sum y product utilizan expresiones de plegado para combinar elegantemente todos los valores sin recursión explícita. Tu función countArgs demuestra cómo sizeof... te proporciona el tamaño del paquete en tiempo de compilación.
Hoja de referencia
Las plantillas variádicas permiten que las plantillas acepten un número arbitrario de argumentos de plantilla utilizando la sintaxis ... (puntos operativos) para crear un paquete de parámetros (parameter pack):
template <typename... Args>
void printAll(Args... args) {
// Args es un paquete de parámetros de plantilla
// args es un paquete de parámetros de función
}Procesa los argumentos utilizando recursividad con un caso base:
// Caso base: no quedan argumentos
void print() {
std::cout << std::endl;
}
// Caso recursivo: procesa el primer argumento, recurre con el resto
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Expande los argumentos restantes
}
print(1, 3.14, "hello", 'x'); // Salida: 1 3.14 hello xUtiliza sizeof... para obtener el número de argumentos en un paquete de parámetros:
template <typename... Args>
size_t countArgs(Args... args) {
return sizeof...(args);
}Utiliza expresiones de plegado (fold expressions) (C++17) para operaciones más sencillas sin recursividad explícita:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Expresión de plegado: suma todos los argumentos
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Salida: 10Las expresiones de plegado funcionan con otros operadores como la multiplicación:
template <typename... Args>
auto product(Args... args) {
return (args * ...); // Multiplica todos los argumentos
}Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// Leer entradas
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: Llamar a printAll con las cuatro entradas
// TODO: Imprimir el conteo de argumentos usando countArgs con intVal, doubleVal y charVal
// Formato: "Argument count: <count>"
// TODO: Calcular e imprimir la suma de intVal, doubleVal, 10 y 5
// Formato: "Sum: <result>"
// TODO: Calcular e imprimir el producto de intVal, 2 y 3
// Formato: "Product: <result>"
// TODO: Llamar a printAll solo con el string
// TODO: Llamar a printAll sin argumentos
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico