Come posso scrivere una funzione che accetta un numero variabile di argomenti? È possibile, come?
Come posso scrivere una funzione che accetta un numero variabile di argomenti? È possibile, come?
Risposte:
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_list
tipo 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.
...
sintassi?
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.
<cstdarg>
in C ++ invece di<stdarg.h>
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 gcc
o clang
possiamo 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. [...]
typename
vs class
è sopra intenzionale? In tal caso, ti preghiamo di spiegare.
initializer_list
ricorsiva?
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 :
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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
e 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...
".
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!
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.
,
operatore con le espressioni fold). Altrimenti, non la penso così.
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' printf
funzione accetta argomenti variabili, l' c++ cout
oggetto utilizza un <<
sovraccarico che affronta la sicurezza dei tipi e gli ADT (forse a costo della semplicità di implementazione).
std::initializer_lists
... E questo sta già introducendo un'enorme complessità in un compito semplice.
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 ++ ...
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.
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 myfunc
con 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 i
e dare solo j
.
È 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.
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...
Utilizzando modelli variadici, esempio per riprodurre console.log
come 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;
}
};
Come altri hanno già detto, varargs in stile C. Ma puoi anche fare qualcosa di simile con argomenti predefiniti.
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);
}
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.
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