6

Tengo una duda sobre como llamar a una función con parámetros variables dentro de otra.

Suena un poco complicado así que pongo un ejemplo:

void MyClass::function1(int ult, ...)
{
    va_list args;
    va_start(args, ult);
    int a = va_arg(args, int); 
    // operaciones con a, un argumento de tipo entero, por poner un ejemplo
    va_end(args);
}

void MyClass::function2(int ult, ...)
{
    //¿como pasar los parámetro (...) a function1?
    function1(ult);
}

Justo en function2 quiero llamar a function1 pasándole los argumentos variables que me pasen en function2.

abulafia
  • 53,696
  • 3
  • 45
  • 80
  • ¿Qué versión de C++? Algunas páginas antiguas (anteriores a 2010) dicen que simplemente crees un método `MyClass::function1(int ult, va_list args)` y que lo invoques tanto desde `MyClass::function1(int ult, ...)` como desde `MyClass::function2(int ult, ...)`, pero igual con las últimas versiones de C++ se puede hacer de forma más elegante. – SJuan76 Nov 27 '18 at 23:20
  • Hola SJuan76, muchas gracias por su respuesta. ¿Puedes ponerme un ejemplo del código de la solución "mas elegante"? – Javier Bandomo Ruiz Nov 28 '18 at 00:13
  • No, lo que quiero decir es que lo que he encontrado es eso, pero no sé si con versiones más modernas de C++ se puede hacer de otra forma. No estaría de más que aclararas que versión de C++ usas. – SJuan76 Nov 28 '18 at 00:16
  • Igual, ¿puedes ponerme un ejemplo de código de la solución que planteas?, no lo entiendo dicho así. Muchas gracias – Javier Bandomo Ruiz Nov 28 '18 at 00:23
  • Ya tienes varias respuestas. Si una de ellas responde a tu pregunta, deberías marcarla como aceptada tal como se muestra en el [tour]. Si hay varias que la respondan, escoge la que te parezca mejor. Si ninguna te responde tu duda, deberías explicar porqué y mejorar tu pregunta para que pueda ser respondida. – SJuan76 Nov 28 '18 at 22:22

3 Answers3

7

Puedes usar variadic templates

#include <iostream>
#include <type_traits> // std::is_same

struct MyClass2 {
  // Declaración genérica de la plantilla. Esta versión no tiene implementación
  template<class ... Args>
  void function1(Args...);

  template<class T, class ... Args>
  void function1(T t, Args ... args) {
    if( std::is_same<T,int>::value )
      std::cout << "entero: " << t << '\n';
    else if( std::is_same<T,double>::value )
      std::cout << "double: " << t << '\n';
    else
      std::cout << t << '\n';

    if( sizeof...(Args) > 0 )
      function1(args...);
  }

  template<class ... Args>
  void function2(Args ... args ) {
    function1(args...);
  }
};

int main( ) {
  MyClass mc;

  mc.function2( 5, 6, 7 );
  mc.function2( 55.66 );

  return 0;
}

La ventaja de esta solución es que el tipado siempre será fuerte. Además ya no hay que indicar cuántos parámetros vas a pasar lo cual es una ventaja ya que ese valor es totalmente redundante y propenso a errores.

eferion
  • 49,291
  • 5
  • 30
  • 72
5

Tal y como te indican en los comentarios, es simple: basta con declarar otra función

MyClass::functionX( int, va_list args );

y llamarla desde tu código:

#include <cstdarg>
#include <iostream>

struct MyClass {
  void function1( int ult, va_list args ) {
    if( !ult ) {
      double db = va_arg( args, double );
      std::cout << "double: " << db << '\n';
    } else {
      for( int idx = 0; idx < ult; ++idx ) {
        int a = va_arg( args, int ); 
        std::cout << "entero: " << a << '\n';
      }
    }
  }

  void function2( int ult, ... ) {
    va_list args;
    va_start( args, ult );
    function1( ult, args );
    va_end( args );
  }
};

int main( ) {
  MyClass mc;

  mc.function2( 3, 5, 6, 7 );
  mc.function2( 0, 55.66 );

  return 0;
}

Basado en esta respuesta de SO

Observa que no es necesario llamar a va_start( ) ni a va_end( ) desde tu función anidada; la pila se limpia correctamente en la llamada a va_end( ) desde la función de 1er nivel.

Nota: para mi sorpresa, funciona bien en funciones-miembro de clases. Creí que this daría algún problema ... pero no ha sido así, funciona perfecto :-O

Trauma
  • 25,297
  • 4
  • 37
  • 60
5

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();
PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
  • Hola Paula_plus_plus, muchas gracias por su respuesta. Si quisiera obtener los parámetros que pasan en "valores" conociendo previamente sus tipos, sin llamar recursivamante a la function1 ¿Cómo lo haría?. Me refiero al equivalente a double db = va_arg( args, double ); – Javier Bandomo Ruiz Nov 29 '18 at 13:19
  • @JavierBandomoRuiz No se si entiendo tu nueva duda, puedes guardar los parámetros en una tupla: `auto t = std::tuple{valores ...};` para extraer valores individuales de una tupla usa `auto valor = std::get<0>(tupla);`. – PaperBirdMaster Nov 29 '18 at 14:07