parameter c++ variadic-functions

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



c++ variadic templates (15)

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


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

template <typename T> void f(std::vector<T> const&); std::vector<int> my_args; my_args.push_back(1); my_args.push_back(2); f(my_args);

De esta manera, obtendría seguridad de tipos y el significado lógico de estos argumentos variad sería evidente.

Seguramente 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 "pitónico" para c ++ ...


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


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

  • ser fuertemente seguro;
  • trabaje sin la información de tiempo de ejecución del número de argumentos, o sin el uso de un argumento de "detención".

Aquí hay un ejemplo para tipos de argumentos mixtos

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

Y otro con el tipo de coincidencia forzada para todos los argumentos:

#include <type_traits> // enable_if, conjuction template<class Head, class... Tail> using are_same = std::conjunction<std::is_same<Head, Tail>...>; template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::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 Variadic, también conocidas como paquete de parámetros Paquete de parámetros (desde C ++ 11) - cppreference.com .
  2. Expresiones de plegado expresión de plegado (desde C ++ 17) - cppreference.com .
  3. Ver una demostración del programa completo en coliru.

En C ++ 11 tiene dos nuevas opciones, como indica la página de referencia de las funciones Variadic en la sección Alternativas :

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

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

#include <iostream> #include <string> #include <initializer_list> template <typename T> void func(T t) { std::cout << t << std::endl ; } template<typename T, typename... Args> void func(T t, Args... args) // recursive variadic function { std::cout << t <<std::endl ; func(args...) ; } template <class T> void func2( std::initializer_list<T> 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 el tipo de firma de la función que puede ser útil para comprender lo que está sucediendo. Por ejemplo usando:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

los resultados serían los siguientes para las funciones variables en el ejemplo ( verlo en vivo ):

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

En Visual Studio puedes 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 <iostream> #include <string> #include <vector> template <class T> void func1( std::vector<T> vec ) { for( typename std::vector<T>::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<int> v1( arr1, arr1+4 ) ; std::vector<std::string> v2( arr2, arr2+2 ) ; func1( v1 ) ; func1( v2 ) ; }

y la alternativa para las plantillas variadic sería funciones variadas aunque no son seguras para los tipos y, en general, propensas a errores y pueden ser inseguras de usar, pero la única alternativa potencial sería utilizar los argumentos predeterminados , aunque eso tiene un uso limitado. El siguiente ejemplo es una versión modificada del código de ejemplo en la referencia vinculada:

#include <iostream> #include <string> #include <cstdarg> 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 conlleva restricciones en los argumentos que puede aprobar, que se detallan en el borrador del estándar C ++ en la sección 5.2.2 llamada a 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 lvalue-to-rvalue (4.1), array-to-pointer (4.2) y function-to-pointer (4.3) se realizan en la expresión de argumento. Después de estas conversiones, si el argumento no tiene aritmética, enumeración, puntero, puntero a miembro o tipo de clase, el programa está mal formado. Si el argumento tiene un tipo de clase no POD (cláusula 9), el comportamiento es indefinido. [...]


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

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


Es posible que desee la sobrecarga o los 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.


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


Las funciones variadicas de estilo C son compatibles con C ++.

Sin embargo, la mayoría de las bibliotecas de C ++ utilizan una expresión idiomática alternativa, por ejemplo, mientras que la función ''c'' printf toma argumentos variables que el objeto c++ cout usa << sobrecarga que aborda la seguridad de tipos y los ADT (quizás a costa de la simplicidad de implementación).


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

Por supuesto, hay argumentos predeterminados que parecen "variables", como el número 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 un número variable de argumentos. Si no se da ninguno, se utilizan los argumentos por defecto. Sin embargo, tenga en cuenta que solo puede omitir los argumentos finales. No hay forma, por ejemplo, de omitir i y dar solo j .


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

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

y así...


También podríamos usar una lista_inicializador si todos los argumentos son constantes y del mismo tipo.


Usando plantillas variadas, ejemplo para reproducir console.log como se ve en JavaScript:

Console console; console.log("bunch", "of", "arguments"); console.warn("or some numbers:", 1, 2, 3); console.error("just a prank", "bro");

Nombre de archivo, por ejemplo, js_console.h :

#include <iostream> #include <utility> class Console { protected: template <typename T> void log_argument(T t) { std::cout << t << " "; } public: template <typename... Args> void log(Args&&... args) { int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } template <typename... Args> void warn(Args&&... args) { cout << "WARNING: "; int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } template <typename... Args> void error(Args&&... args) { cout << "ERROR: "; int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } };


en c ++ 11 puedes hacer:

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

inicializador de lista FTW!


Probablemente no deberías, y probablemente puedas hacer lo que quieras hacer de una manera más segura y sencilla. Técnicamente, para usar el número 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<stdarg.h> 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 intentas lograr conceptualmente. En su lugar, considere utilizar la sobrecarga o la herencia / polimorfismo, el patrón de creación (como en el operator<<() en las secuencias) o los argumentos predeterminados, etc. Todos estos son más seguros: el compilador llega a saber más sobre lo que está tratando de hacer, así que hay más ocasiones puede detenerte antes de que te sople la pierna.


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 sencilla