¿Número variable de argumentos en C ++?

¿Cómo puedo escribir una función que acepte una cantidad variable de argumentos? Es esto posible, ¿cómo?

Probablemente no deberías, y probablemente puedas hacer lo que quieras de una manera más segura y simple. Técnicamente para usar la cantidad variable de argumentos en C, incluye stdarg.h. De ahí obtendrá el tipo va_list y tres funciones que operan en él llamadas va_start() , va_arg() y va_end() .

 #include int maxof(int n_args, ...) { va_list ap; va_start(ap, n_args); int max = va_arg(ap, int); for(int i = 2; i <= n_args; i++) { int a = va_arg(ap, int); if(a > max) max = a; } va_end(ap); return max; } 

Si me preguntas, esto es un desastre. Se ve mal, es inseguro y está lleno de detalles técnicos que no tienen nada que ver con lo que estás tratando de lograr conceptualmente. En cambio, considere usar sobrecarga o herencia / polymorphism, patrón de comstackdor (como en el operator<<() en las transmisiones) o argumentos predeterminados, etc. Todos estos son más seguros: el comstackdor sabe más acerca de lo que está tratando de hacer para que haya en más ocasiones puede detenerte antes de volar la pierna.

En C ++ 11 tiene dos opciones nuevas, como dice la página de referencias de funciones variables en la sección Alternativas :

  • Las plantillas variables también se pueden usar para crear funciones que toman una cantidad variable de argumentos. A menudo son la mejor opción porque no imponen restricciones sobre los tipos de argumentos, no realizan promociones integrales y de punto flotante, y son de tipo seguro. (desde C ++ 11)
  • Si todos los argumentos de variables comparten un tipo común, std :: initializer_list proporciona un mecanismo conveniente (aunque con una syntax diferente) para acceder a los argumentos variables.

A continuación se muestra un ejemplo que muestra ambas alternativas ( ver en vivo ):

 #include  #include  #include  template  void func(T t) { std::cout << t << std::endl ; } template void func(T t, Args... args) // recursive variadic function { std::cout << t < void func2( std::initializer_list list ) { for( auto elem : list ) { std::cout << elem << std::endl ; } } int main() { std::string str1( "Hello" ), str2( "world" ); func(1,2.5,'a',str1); func2( {10, 20, 30, 40 }) ; func2( {str1, str2 } ) ; } 

Si está utilizando gcc o clang , podemos usar la variable mágica PRETTY_FUNCTION para mostrar la firma de tipo de la función que puede ser útil para comprender lo que está sucediendo. Por ejemplo usando:

 std::cout << __PRETTY_FUNCTION__ << ": " << t < 

would results int following para funciones variadas en el ejemplo ( verlo en vivo ):

 void func(T, Args...) [T = int, Args = >]: 1 void func(T, Args...) [T = double, Args = >]: 2.5 void func(T, Args...) [T = char, Args = >]: a void func(T) [T = std::basic_string]: Hello 

En Visual Studio puede usar FUNCSIG .

Actualizar Pre C ++ 11

Pre C ++ 11 la alternativa para std :: initializer_list sería std :: vector o uno de los otros contenedores estándar :

 #include  #include  #include  template  void func1( std::vector vec ) { for( typename std::vector::iterator iter = vec.begin(); iter != vec.end(); ++iter ) { std::cout << *iter << std::endl ; } } int main() { int arr1[] = {10, 20, 30, 40} ; std::string arr2[] = { "hello", "world" } ; std::vector v1( arr1, arr1+4 ) ; std::vector v2( arr2, arr2+2 ) ; func1( v1 ) ; func1( v2 ) ; } 

y la alternativa para las plantillas variadic sería funciones variadas aunque no sean seguras para el tipo de letra y en general sean propensas a errores y no sean seguras, pero la única otra alternativa posible sería usar argumentos predeterminados , aunque eso tiene un uso limitado. El siguiente ejemplo es una versión modificada del código de muestra en la referencia vinculada:

 #include  #include  #include  void simple_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); while (*fmt != '\0') { if (*fmt == 'd') { int i = va_arg(args, int); std::cout << i << '\n'; } else if (*fmt == 's') { char * s = va_arg(args, char*); std::cout << s << '\n'; } ++fmt; } va_end(args); } int main() { std::string str1( "Hello" ), str2( "world" ); simple_printf("dddd", 10, 20, 30, 40 ); simple_printf("ss", str1.c_str(), str2.c_str() ); return 0 ; } 

El uso de funciones variadic también viene con restricciones en los argumentos que puede aprobar que se detallan en el borrador del estándar de C ++ en la sección 5.2.2 Llamada de función párrafo 7 :

Cuando no hay un parámetro para un argumento dado, el argumento se pasa de tal manera que la función receptora puede obtener el valor del argumento invocando va_arg (18.7). Las conversiones estándar de lvalue a rvalue (4.1), de matriz a puntero (4.2) y de función a puntero (4.3) se realizan en la expresión del argumento. Después de estas conversiones, si el argumento no tiene aritmética, enumeración, puntero, puntero a miembro o tipo de clase, el progtwig está mal formado. Si el argumento tiene un tipo de clase no POD (cláusula 9), el comportamiento no está definido. [...]

Las funciones variadic de estilo C se admiten en C ++.

Sin embargo, la mayoría de las bibliotecas de C ++ usan un modismo alternativo, por ejemplo, mientras que la función 'c' printf toma argumentos variables el objeto c++ cout usa << sobrecarga que aborda seguridad de tipo y ADT (quizás a costa de simplicidad de implementación).

En C ++ 11 hay una manera de hacer plantillas de argumentos variables que conducen a una manera realmente elegante y segura de tener funciones de argumentos variables. El mismo Bjarne da un buen ejemplo de printf usando plantillas de argumentos variables en C ++ 11FAQ .

Personalmente, considero esto tan elegante que ni siquiera me molestaría con una función de argumento variable en C ++ hasta que ese comstackdor tenga soporte para las plantillas de argumentos de variable de C ++ 11.

Además de varargs o sobrecarga, podría considerar agregar sus argumentos en std :: vector u otros contenedores (std :: map por ejemplo). Algo como esto:

 template  void f(std::vector const&); std::vector my_args; my_args.push_back(1); my_args.push_back(2); f(my_args); 

De esta forma ganarías seguridad de tipo y el significado lógico de estos argumentos variados sería aparente.

Sin duda, este enfoque puede tener problemas de rendimiento, pero no debe preocuparse por ellos a menos que esté seguro de que no puede pagar el precio. Es una especie de enfoque a “Pythonic” para c ++ …

en c ++ 11 puedes hacer:

 void foo(const std::list & myArguments) { //do whatever you want, with all the convenience of lists } foo({"arg1","arg2"}); 

lista inicializador FTW!

La única forma es mediante el uso de argumentos de variables de estilo C, como se describe aquí . Tenga en cuenta que esta no es una práctica recomendada, ya que no es segura y propensa a errores.

No hay una forma estándar de C ++ para hacer esto sin recurrir a varargs C-style ( ... ).

Por supuesto, hay argumentos predeterminados que se “parecen” a la cantidad variable de argumentos según el contexto:

 void myfunc( int i = 0, int j = 1, int k = 2 ); // other code... myfunc(); myfunc( 2 ); myfunc( 2, 1 ); myfunc( 2, 1, 0 ); 

Las cuatro llamadas de función llaman a myfunc con una cantidad variable de argumentos. Si no se proporciona ninguno, se usan los argumentos predeterminados. Sin embargo, tenga en cuenta que solo puede omitir los argumentos finales. No hay forma de, por ejemplo, omitir i y dar solo j .

Es posible que desee sobrecargar o parámetros predeterminados: defina la misma función con los parámetros predeterminados:

 void doStuff( int a, double termstator = 1.0, bool useFlag = true ) { // stuff } void doStuff( double std_termstator ) { // assume the user always wants '1' for the a param return doStuff( 1, std_termstator ); } 

Esto le permitirá llamar al método con una de cuatro llamadas diferentes:

 doStuff( 1 ); doStuff( 2, 2.5 ); doStuff( 1, 1.0, false ); doStuff( 6.72 ); 

… o podría estar buscando las convenciones de llamada v_args de C.

Desde la introducción de plantillas variadic en C ++ 11 y expresiones fold en C ++ 17, es posible definir una función de plantilla que, en el sitio llamado, se puede llamar como si fuera una función varidic pero con las ventajas de :

  • ser fuerte tipo seguro;
  • trabajar sin la información de tiempo de ejecución del número de argumentos, o sin el uso de un argumento “detener”.

Aquí hay un ejemplo para tipos de argumentos mixtos

 template void print(Args... args) { (std::cout << ... << args) << "\n"; } print(1, ':', " Hello", ',', " ", "World!"); 

Y otro con una coincidencia de tipos forzada para todos los argumentos:

 #include  // enable_if, conjuction template using are_same = std::conjunction...>; template::value, void>> void print_same_type(Head head, Tail... tail) { std::cout << head; (std::cout << ... << tail) << "\n"; } print_same_type("2: ", "Hello, ", "World!"); // OK print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])' print_same_type(3, ": ", "Hello, ", "World!"); ^ 

Más información:

  1. Plantillas variables, también conocidas como paquete de parámetro paquete de parámetros (desde C ++ 11) - cppreference.com .
  2. Expresiones plegadas expresión de plegado (desde C ++ 17) - cppreference.com .
  3. Vea una demostración completa del progtwig en coliru.

Como otros han dicho, varargs C-style. Pero también puedes hacer algo similar con los argumentos predeterminados.

Si conoce el rango de la cantidad de argumentos que se proporcionarán, siempre puede usar alguna sobrecarga de funciones, como

 f(int a) {int res=a; return res;} f(int a, int b) {int res=a+b; return res;} 

y así…

 int fun(int n_args, ...) { int *p = &n_args; int s = sizeof(int); p += s + s - 1; for(int i = 0; i < n_args; i++) { printf("A1 %d!\n", *p); p += 2; } } 

Versión simple

También podríamos usar una initializer_list si todos los argumentos son const y del mismo tipo