La elipsis (...
) es el mecanismo de C++ para redactar funciones con argumentos variádicos , existen dos formas (muy diferentes) de trabajar con la elipsis:
C++ tradicional (previo al estándar C++11).
No voy a entrar en detalles de esta manera de trabajar con la elipsis porque sus desventajas:
- Sintaxis confusa ¡apoyada en macros!.
- Propenso a errores: si las macros no se escriben en el orden y de la manera adecuada, el código falla en tiempo de ejecución de maneras impredecibles.
- Vulnerable: un código que use mal la elipsis tradicional puede facilitar el hackeo del sistema.
- Sin seguridad de tipos: los tipos pasados en la elipsis se transforman a otros tipos, no conservan su tipado original (y no se les puede pasar objetos de manera natural).
- Dependiente de C y sus cabeceras.
Superan con creces a sus ventajas:
- Es la manera más conocida.
C++ moderno (C++11 en adelante).
El estándar C++11 incorporó las plantillas variádicas, estas plantillas permiten crear funciones y objetos (y a partir de C++20 también lambdas) que reciban una cantidad arbitraria de argumentos de tipos arbitrarios, sus ventajas:
- Tipado fuerte.
- Trabaja con objetos de manera natural.
- Los errores se detectan en tiempo de compilación.
Superan con creces a sus desventajas:
- Sintaxis confusa.
- Los errores de compilación son muy confusos.
- Aumenta el tiempo de compilación.
Una función con parámetros variádicos tiene este aspecto:
template <typename ... VALORES>
/* ^^^ <--- La elipsis indica que tendremos un paquete de valores
el nombre del paquete de valores es VALORES. */
void function1(VALORES ... valores)
/* ^^^^^^^ <--- Usamos el paquete de valores, 'VALORES ...' es el tipo
mientras que 'valores' son los argumentos. */
{
}
Si tuviéramos otra función con parámetros variádicos y quisiéramos pasar el paquete de parámetros a funcion1
lo haríamos así:
template <typename ... VALORES>
void function2(int ult, VALORES ... valores)
{
function1(valores ...);
// ^^^^^^^ <--- Usamos los valores como argumento para function1
}
Observa que cada vez que tratamos con argumentos variádicos, la parte variádica (ya sean los tipos o los valores) va acompañada de la elipsis.
La elipsis obtiene automáticamente los tipos y valores facilitados, así que si se llamase a function2
con 2 parámetros:
function2(0, 100l);
El 0
iría a parar al argumento ult
y el 100l
iría la paquete de parámetros, que tendría un solo elemento de valor 100 y de tipo long
; llamada con 6 parámetros:
function2(1, "patata", std::string{"frita"}, 'a', 100l, nullptr, 1 + 2 + 0.f);
El 1
iría a parar al argumento ult
y el paquete de parámetros tendría como tipos const char *
, std::string
, char
, long
, nullptr_t
y float
con los valores que hemos visto antes; llamada con 1 parámetro:
function2(2);
El 2
iría a parar al argumento ult
y el paquete de parámetros estaría vacío, lo que equivaldría a llamar a function1
vacía:
function1();