Numero variabile di argomenti in C ++?


293

Come posso scrivere una funzione che accetta un numero variabile di argomenti? È possibile, come?


49
In questo momento con C ++ 11 le risposte a questa domanda sarebbero molto diverse
K-ballo

1
@ K-ballo Ho aggiunto esempi C ++ 11 poiché una recente domanda ha posto recentemente questa stessa cosa e ne ho sentito il bisogno per giustificarne la chiusura stackoverflow.com/questions/16337459/…
Shafik Yaghmour

1
Aggiunte opzioni pre C ++ 11 anche alla mia risposta, quindi ora dovrebbe coprire la maggior parte delle scelte disponibili.
Shafik Yaghmour,

@ K-ballo non c'è modo di farlo in C ++ nel caso in cui tu abbia bisogno di un tipo di argomento forzato .. nessuna costruzione come foo (valori int ...): / Se non ti interessa i tipi, allora sì, modelli variabili in C ++ 11 funziona alla grande
graywolf,

Risposte:


152

Probabilmente non dovresti, e probabilmente puoi fare quello che vuoi fare in un modo più sicuro e semplice. Tecnicamente per usare un numero variabile di argomenti in C includi stdarg.h. Da questo otterrai il va_listtipo e tre funzioni che operano su di esso chiamato va_start(), va_arg()e 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;
}

Se me lo chiedi, questo è un casino. Sembra male, non è sicuro ed è pieno di dettagli tecnici che non hanno nulla a che fare con ciò che stai concettualmente cercando di ottenere. Invece, considera l'utilizzo di overload o ereditarietà / polimorfismo, modello di generatore (come negli operator<<()stream) o argomenti predefiniti ecc. Questi sono tutti più sicuri: il compilatore viene a sapere di più su ciò che stai cercando di fare, quindi ci sono più occasioni che può fermare prima di farti saltare la gamba.


7
Presumibilmente, non è possibile passare riferimenti a una funzione varargs perché il compilatore non saprebbe quando passare per valore e quando per riferimento e poiché le macro C sottostanti non saprebbero necessariamente cosa fare con i riferimenti - ci sono già restrizioni su cosa puoi passare a una funzione C con argomenti variabili a causa di cose come le regole di promozione.
Jonathan Leffler,

3
è necessario fornire almeno un argomento prima della ...sintassi?
Lazer,

3
@Lazer non è un requisito di lingua o di libreria, ma la libreria standard non ti dà i mezzi per dire la lunghezza dell'elenco. Hai bisogno del chiamante per darti queste informazioni o in qualche modo capirle da solo. Nel caso printf(), ad esempio, la funzione analizza l'argomento stringa per i token speciali per capire quanti argomenti extra dovrebbe aspettarsi nell'elenco degli argomenti variabili.
Wilhelmtell,

11
dovresti probabilmente usare <cstdarg>in C ++ invece di<stdarg.h>
newacct

11
Il numero variabile di argomenti è ottimo per il debug o per funzioni / metodi che riempiono alcuni array. Inoltre è ottimo per molte operazioni matematiche, come max, min, sum, average ... Non è un casino quando non si scherza.
Tomáš Zato - Ripristina Monica il

395

In C ++ 11 hai due nuove opzioni, come afferma la pagina di riferimento delle funzioni Variadic nella sezione Alternative :

  • I modelli variabili possono anche essere utilizzati per creare funzioni che accettano un numero variabile di argomenti. Spesso sono la scelta migliore perché non impongono restrizioni sui tipi di argomenti, non svolgono promozioni integrali e in virgola mobile e sono sicure per i tipi. (dal C ++ 11)
  • Se tutti gli argomenti variabili condividono un tipo comune, un elenco std :: initializer_list fornisce un comodo meccanismo (sebbene con una sintassi diversa) per accedere agli argomenti variabili.

Di seguito è riportato un esempio che mostra entrambe le alternative ( vederlo dal 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 } ) ;
} 

Se stai utilizzando gcco clangpossiamo usare la variabile magica PRETTY_FUNCTION per visualizzare la firma del tipo della funzione che può essere utile per capire cosa sta succedendo. Ad esempio utilizzando:

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

risulterebbe int seguente per le funzioni variadiche nell'esempio ( vederlo dal 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

In Visual Studio è possibile utilizzare FUNCSIG .

Aggiorna Pre C ++ 11

Pre C ++ 11 l'alternativa per std :: initializer_list sarebbe std :: vector o uno degli altri contenitori standard :

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

e l'alternativa per i modelli variadici sarebbero le funzioni variadiche anche se non sono sicure per i tipi e in genere soggette a errori e possono essere non sicure da usare, ma l'unica alternativa potenziale sarebbe usare argomenti predefiniti , sebbene ciò abbia un uso limitato. L'esempio seguente è una versione modificata del codice di esempio nel riferimento collegato:

#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 ;
} 

L'uso delle funzioni variadiche comporta anche delle restrizioni negli argomenti che è possibile passare, che è dettagliato nella bozza dello standard C ++ nella sezione 5.2.2 Funzione chiamata paragrafo 7 :

Quando non esiste alcun parametro per un determinato argomento, l'argomento viene passato in modo tale che la funzione di ricezione possa ottenere il valore dell'argomento invocando va_arg (18.7). Le conversioni standard lvalue-to-rvalue (4.1), array-to-pointer (4.2) e function-to-pointer (4.3) vengono eseguite sull'espressione argomento. Dopo queste conversioni, se l'argomento non ha aritmetica, enumerazione, puntatore, puntatore al membro o tipo di classe, il programma non è corretto. Se l'argomento ha un tipo di classe non POD (clausola 9), il comportamento non è definito. [...]


Il tuo utilizzo typenamevs classè sopra intenzionale? In tal caso, ti preghiamo di spiegare.
kevinarpe,

1
@kevinarpe non è intenzionale, tuttavia non cambia nulla.
Shafik Yaghmour,

Il tuo primo link dovrebbe probabilmente essere probabilmente en.cppreference.com/w/cpp/language/variadic_arguments .
Alexey Romanov,

è possibile effettuare una funzione prendendo una initializer_listricorsiva?
idclev 463035818,

33

Una soluzione C ++ 17: sicurezza di tipo completo + bella sintassi di chiamata

Dall'introduzione dei modelli variadici in C ++ 11 e delle espressioni fold in C ++ 17, è possibile definire una funzione template che, nel sito del chiamante, è richiamabile come se fosse una funzione varidica ma con i vantaggi di :

  • essere fortemente sicuro di scrivere;
  • funziona senza le informazioni di runtime del numero di argomenti o senza l'utilizzo di un argomento "stop".

Ecco un esempio per tipi di argomenti misti

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

E un altro con tipo forzato corrisponde per tutti gli argomenti:

#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!");
                                                                                              ^

Maggiori informazioni:

  1. Modelli variabili, noto anche come pacchetto di parametri Pacchetto di parametri (dal C ++ 11) - cppreference.com .
  2. Piega espressioni piega espressione (dal C ++ 17) - cppreference.com .
  3. Guarda una dimostrazione completa del programma su coliru.

13
un giorno spero di poter leggeretemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian il

1
@Eladian lo legge come "Questa cosa è abilitata solo se Heade Tail... sono uguali ", dove " sono gli stessi " significa std::conjunction<std::is_same<Head, Tail>...>. Leggi quest'ultima affermazione come " Headè uguale a tutto Tail...".
YSC

24

in c ++ 11 puoi fare:

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

foo({"arg1","arg2"});

lista inizializzatore FTW!


19

In C ++ 11 esiste un modo per creare modelli di argomenti variabili che portano a un modo davvero elegante e sicuro di digitare funzioni con argomenti variabili. Bjarne stesso fornisce un bell'esempio di printf usando modelli di argomenti variabili in C ++ 11FAQ .

Personalmente, lo considero così elegante che non mi preoccuperei nemmeno di una funzione di argomento variabile in C ++ fino a quando quel compilatore non avrà il supporto per i modelli di argomento variabile C ++ 11.


@donlan - Se usi C ++ 17, puoi usare le espressioni fold per rendere le cose molto più semplici in alcuni casi (pensa in modo creativo qui, puoi usare l' ,operatore con le espressioni fold). Altrimenti, non la penso così.
Onnipotente il

15

Le funzioni variadiche di tipo C sono supportate in C ++.

Tuttavia, la maggior parte delle librerie C ++ usa un linguaggio alternativo, ad es. Mentre la 'c' printffunzione accetta argomenti variabili, l' c++ coutoggetto utilizza un <<sovraccarico che affronta la sicurezza dei tipi e gli ADT (forse a costo della semplicità di implementazione).


Inoltre, questo sembra funzionare solo nel caso di una funzione come la stampa, in cui si ha effettivamente un'iterazione di una singola funzione argomento in ogni argomento. Altrimenti, stai solo inizializzando un elenco e passando l'elenco per la fine std::initializer_lists... E questo sta già introducendo un'enorme complessità in un compito semplice.
Christopher

13

Oltre a varargs o sovraccarico, potresti considerare di aggregare i tuoi argomenti in uno std :: vector o in altri contenitori (ad esempio std :: map). Qualcosa come questo:

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

In questo modo otterresti la sicurezza del tipo e il significato logico di questi argomenti variadici sarebbe evidente.

Sicuramente questo approccio può avere problemi di prestazioni, ma non dovresti preoccuparti di loro a meno che tu non sia sicuro di non poter pagare il prezzo. È una sorta di approccio "Pythonic" al c ++ ...


6
Più pulito sarebbe non far rispettare i vettori. Utilizzare invece un argomento modello che specifica la raccolta in stile STL, quindi scorrere attraverso di essa utilizzando i metodi di inizio e fine dell'argomento. In questo modo puoi usare std :: vector <T>, c ++ 11's std :: array <T, N>, std :: initializer_list <T> o persino creare la tua collezione.
Jens Åkerblom,

3
@ JensÅkerblom Sono d'accordo, ma questo è il tipo di scelta che dovrebbe essere analizzato per il problema in questione, per evitare un eccesso di ingegneria. Dato che si tratta della firma dell'API, è importante comprendere il compromesso tra massima flessibilità e chiarezza di intenti / usabilità / manutenibilità ecc.
Francesco

8

L'unico modo è attraverso l'uso di argomenti variabili in stile C, come descritto qui . Si noti che questa non è una pratica raccomandata, in quanto non è tipesa e soggetta ad errori.


Per errore, presumo che intendi potenzialmente molto, molto pericoloso? Soprattutto quando si lavora con input non attendibili.
Kevin Loney,

Sì, ma a causa dei problemi di sicurezza del tipo. Pensa a tutti i possibili problemi che ha il normale printf: stringhe di formato che non corrispondono agli argomenti passati e così via. printf usa la stessa tecnica, BTW.
Dave Van den Eynde,

7

Non esiste un modo C ++ standard per farlo senza ricorrere a varargs in stile C ( ...).

Esistono ovviamente argomenti predefiniti che sembrano "assomigliare" al numero variabile di argomenti a seconda del contesto:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Tutte e quattro le chiamate di funzione chiamano myfunccon un numero variabile di argomenti. Se non ne viene fornito nessuno, vengono utilizzati gli argomenti predefiniti. Si noti tuttavia che è possibile omettere solo argomenti finali. Non c'è modo, ad esempio di omettere ie dare solo j.


4

È possibile che si desideri sovraccaricare o parametri predefiniti: definire la stessa funzione con parametri predefiniti:

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

Ciò ti consentirà di chiamare il metodo con una delle quattro diverse chiamate:

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

... oppure potresti cercare le convenzioni di chiamata v_args di C.


2

Se conosci l'intervallo del numero di argomenti che verranno forniti, puoi sempre usare un sovraccarico di funzioni, come

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

e così via...


2

Utilizzando modelli variadici, esempio per riprodurre console.logcome visto in JavaScript:

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

Nome file ad es . 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;
    }
};


0

Ora è possibile ... usando boost any e template In questo caso, è possibile combinare il tipo di argomenti

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

0

Combina le soluzioni C e C ++ per l'opzione semanticamente più semplice, performante e più dinamica. Se sbagli, prova qualcos'altro.

// 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;
}

L'utente scrive:

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

Semanticamente, questo sembra e sembra esattamente una funzione n-argomento. Sotto il cofano, potresti disimballarlo in un modo o nell'altro.


-1

Potremmo anche usare un initializer_list se tutti gli argomenti sono const e dello stesso tipo


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

Versione normale


1
E un comportamento indefinito che non funzionerà su qualcosa di diverso da x86.
SS Anne
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.