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

293

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

nunos
fuente
49
En este momento con C ++ 11 las respuestas para esta pregunta diferirían en gran medida
K-ballo
1
@ K-balloon También agregué ejemplos de C ++ 11 ya que una pregunta reciente hizo esta misma pregunta recientemente y sentí que necesitaba una para justificar el cierre stackoverflow.com/questions/16337459/…
Shafik Yaghmour
1
También agregué opciones anteriores a C ++ 11 a mi respuesta, por lo que ahora debería cubrir la mayoría de las opciones disponibles.
Shafik Yaghmour
@ K-balloon no hay forma de hacerlo en C ++ en caso de que necesite un tipo de argumento forzado ... sin construcción como foo (int ... valores): / Si no le importan los tipos, entonces sí, plantillas variadas en C ++ 11 funciona muy bien
graywolf

Respuestas:

152

Probablemente no deberías, y probablemente puedas hacer lo que quieras hacer de una manera más segura y sencilla. Técnicamente, para usar un número variable de argumentos en C, incluye stdarg.h. A partir de eso, obtendrá el va_listtipo y las 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 estás tratando de lograr conceptualmente. En su lugar, considere usar sobrecarga o herencia / polimorfismo, patrón de construcción (como en las operator<<()secuencias) o argumentos predeterminados, etc. Todo esto es más seguro: el compilador conoce más sobre lo que está tratando de hacer, por lo que hay más ocasiones en que puede detenerse antes de que te vueles la pierna.

Wilhelmtell
fuente
77
Presumiblemente, no puede pasar referencias a una función varargs porque el compilador no sabría cuándo pasar por valor y cuándo por referencia, y porque las macros C subyacentes no sabrían necesariamente qué hacer con las referencias: ya hay restricciones sobre qué puede pasar a una función C con argumentos variables debido a cosas como las reglas de promoción.
Jonathan Leffler el
3
¿Es necesario proporcionar al menos un argumento antes de la ...sintaxis?
Lazer
3
@Lazer no es un requisito de idioma o biblioteca, pero la biblioteca estándar no le da medios para indicar la longitud de la lista. Necesita que la persona que llama le brinde esta información o, de lo contrario, descúbrela usted mismo. En el caso de printf(), por ejemplo, la función analiza el argumento de cadena para tokens especiales para determinar cuántos argumentos adicionales debe esperar en la lista de argumentos variables.
wilhelmtell
11
probablemente deberías usar <cstdarg>en C ++ en lugar de<stdarg.h>
newacct
11
El número variable de argumentos es excelente para la depuración o para funciones / métodos que llenan alguna matriz. También es ideal para muchas operaciones matemáticas, como max, min, sum, average ... No es un desastre cuando no lo haces.
Tomáš Zato - Restablece a Mónica el
395

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

  • Las plantillas variables 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 sobre los tipos de argumentos, no realizan promociones integrales y de coma 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 gcco clangpodemos utilizar 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 <<std::endl ;

daría como resultado int following para funciones variadas 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 plantillas variadas serían funciones variadas, aunque no son de tipo seguro y en general propensas a errores y pueden ser inseguras de usar, pero la única otra alternativa potencial 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 <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 variadas también viene con restricciones en los argumentos que puede pasar, que se detalla en el borrador del estándar C ++ en la sección 5.2.2 Llamada de función, párrafo 7 :

Cuando no hay ningún 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 que no es POD (cláusula 9), el comportamiento es indefinido. [...]

Shafik Yaghmour
fuente
¿Su uso typenamevs es classsuperior al intencional? Si es así, por favor explique.
kevinarpe
1
@kevinarpe no es intencional, aunque no cambia nada.
Shafik Yaghmour
Su primer enlace probablemente debería ser en.cppreference.com/w/cpp/language/variadic_arguments en su lugar.
Alexey Romanov
¿Es posible hacer una función tomando un initializer_listrecursivo?
idclev 463035818
33

Una solución C ++ 17: seguridad de tipo completo + buena sintaxis de llamadas

Desde la introducción de plantillas variadas en C ++ 11 y expresiones de plegado en C ++ 17, es posible definir una función de plantilla que, en el sitio de la persona que llama, es invocable como si fuera una función varídica 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 "stop".

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 concordancia de tipo 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 variables, también conocidas como paquete de parámetros Paquete de parámetros (desde C ++ 11) - cppreference.com .
  2. Expresiones de pliegue expresión de pliegue (desde C ++ 17) - cppreference.com .
  3. Vea una demostración completa del programa en coliru.
YSC
fuente
13
algún día espero que pueda leertemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian
1
@Eladian lo leyó como "Esto está habilitado solo si Heady Tail... son lo mismo ", donde " son lo mismo " significa std::conjunction<std::is_same<Head, Tail>...>. Lea esta última definición como " Heades lo mismo que todos Tail...".
YSC
24

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"});

lista inicializador FTW!

Markus Zancolò
fuente
19

En C ++ 11 hay una manera de hacer plantillas de argumentos variables que conducen 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 esto tan elegante que ni siquiera me molestaría con una función de argumento variable en C ++ hasta que ese compilador sea compatible con las plantillas de argumento variable de C ++ 11.

De todo género
fuente
@donlan: si usa C ++ 17, puede usar expresiones de pliegue para simplificar las cosas en algunos casos (piense creativamente aquí, puede usar el ,operador con expresiones de pliegue). De lo contrario, no lo creo.
Omnifarioso
15

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

Sin embargo, la mayoría de las bibliotecas de C ++ usan un idioma alternativo, por ejemplo, mientras que la 'c' printffunción toma argumentos variables, el c++ coutobjeto usa una <<sobrecarga que aborda la seguridad de tipos y los ADT (quizás a costa de la simplicidad de implementación).

Será
fuente
Además, esto solo parece funcionar en el caso de una función como la impresión, donde realmente tiene una iteración de una función de argumento único en cada argumento. De lo contrario, solo está inicializando una lista y pasando la lista para el final std::initializer_lists... Y esto ya está introduciendo una complejidad masiva en una tarea simple.
Christopher
13

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 forma, obtendría seguridad de tipo y el significado lógico de estos argumentos variados 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ón" para c ++ ...

Francesco
fuente
66
Más limpio sería no imponer vectores. En su lugar, use un argumento de plantilla que especifique la colección de estilo STL y luego repítalo utilizando los métodos de inicio y fin del argumento. De esta manera, puede usar std :: vector <T>, c ++ 11's std :: array <T, N>, std :: initializer_list <T> o incluso hacer su propia colección.
Jens Åkerblom
3
@ JensÅkerblom Estoy de acuerdo, pero este es el tipo de elección que debe analizarse para el problema en cuestión, para evitar la ingeniería excesiva. Dado que se trata de una firma API, es importante comprender la compensación entre la máxima flexibilidad y la claridad de la intención / usabilidad / mantenibilidad, etc.
Francesco
8

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

Dave Van den Eynde
fuente
Por propenso a errores, supongo que te refieres a potencialmente muy muy peligroso. Especialmente cuando se trabaja con entrada no confiable.
Kevin Loney el
Sí, pero debido al tipo de problemas de seguridad. Piense en todos los posibles problemas que tiene printf regular: cadenas de formato que no coinciden con los argumentos pasados, y tal. printf usa la misma técnica, por cierto.
Dave Van den Eynde
7

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

Por supuesto, hay argumentos predeterminados que "parecen" como un 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 a funciones llaman myfunccon un número variable de argumentos. Si no se proporciona ninguno, se utilizan los argumentos predeterminados. Sin embargo, tenga en cuenta que solo puede omitir argumentos finales. No hay forma, por ejemplo, de omitir iy dar solo j.

Zoli
fuente
4

Es posible que desee una sobrecarga 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ías estar buscando las convenciones de llamadas v_args de C.

Kieveli
fuente
2

Si conoce el rango de número 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í...

Dunya Degirmenci
fuente
2

Usando plantillas variadas, ejemplo para reproducir console.logcomo 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;
    }
};
lama12345
fuente
1

Como han dicho otros, varargs de estilo C. Pero también puede hacer algo similar con argumentos predeterminados.

Thomas Padron-McCarthy
fuente
¿Podrías dar más detalles?
Financia la demanda de Mónica
@QPaysTaxes: para ver un ejemplo de cómo hacerlo con argumentos predeterminados, consulte la respuesta de Zoltan.
Thomas Padron-McCarthy
0

Es posible ahora ... usando boost any y templates En este caso, el tipo de argumentos se puede mezclar

#include <boost/any.hpp>
#include <iostream>

#include <vector>
using boost::any_cast;

template <typename T, typename... Types> 
void Alert(T var1,Types... var2) 
{ 

    std::vector<boost::any> a(  {var1,var2...});

    for (int i = 0; i < a.size();i++)
    {

    if (a[i].type() == typeid(int))
    {
        std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(double))
    {
        std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(const char*))
    {
        std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
    }
    // etc
    }

} 


void main()
{
    Alert("something",0,0,0.3);
}
Réplica
fuente
0

Combine las soluciones C y C ++ para obtener la opción semánticamente más simple, eficiente y dinámica. Si te equivocas, prueba con otra cosa.

// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
  T * arr = new T[n];
  va_list ap;
  va_start(ap, n);
  for (size_t i = 0; i < n; i++)
    T[i] = va_arg(ap,T);
  return arr;
}

El usuario escribe:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

Semánticamente, esto se ve y se siente exactamente como una función de argumento n. Debajo del capó, puede desempacarlo de una manera u otra.

Christopher
fuente
-1

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

pkumar0
fuente
-2
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

Вениамин Раскин
fuente
1
Y un comportamiento indefinido que no funcionará en otra cosa que no sea x86.
SS Anne