Variable Anzahl von Argumenten in C ++?

Wie kann ich eine function schreiben, die eine variable Anzahl von Argumenten akzeptiert? Ist das möglich, wie?

Solutions Collecting From Web of "Variable Anzahl von Argumenten in C ++?"

Sie sollten es wahrscheinlich nicht, und Sie können wahrscheinlich tun, was Sie auf eine sicherere und einfachere Weise tun möchten. Um technisch eine variable Anzahl von Argumenten in C zu verwenden, fügen Sie stdarg.h hinzu. Daraus erhalten Sie den va_list Typ sowie drei functionen, die darauf va_start() : va_start() , va_arg() und 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; } 

Wenn Sie mich fragen, ist das ein Durcheinander. Es sieht schlecht aus, es ist unsicher und es steckt voller technischer Details, die nichts mit dem zu tun haben, was Sie konzeptionell erreichen wollen. Ziehen Sie stattdessen Überladung oder inheritance / Polymorphie, Builder-Muster (wie in operator< <() in Streams) oder Standardargumente in Betracht. Diese sind alle sicherer: Der Compiler erfährt mehr darüber, was Sie zu tun versuchen mehr Gelegenheiten kann es dich stoppen, bevor du dein Bein weg bläst.

In C ++ 11 haben Sie zwei neue Optionen, wie die Referenzseite der Variadic-functionen im Abschnitt Alternativen angibt:

  • Variadische Vorlagen können auch verwendet werden, um functionen mit einer variablen Anzahl von Argumenten zu erstellen. Sie sind oft die bessere Wahl, da sie den Typen der Argumente keine Beschränkungen auferlegen, keine Integral- und Gleitkomma-Aktionen ausführen und typsicher sind. (seit C ++ 11)
  • Wenn alle Variablenargumente einen gemeinsamen Typ haben, bietet std :: initializer_list einen bequemen Mechanismus (wenn auch mit einer anderen Syntax) für den Zugriff auf Variablenargumente.

Unten sehen Sie ein Beispiel, das beide Alternativen zeigt ( siehe Live ):

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

Wenn Sie gcc oder clang , können Sie die magische Variable PRETTY_FUNCTION verwenden, um die Typensignatur der function anzuzeigen, die hilfreich sein kann, um zu verstehen, was vor sich geht. Zum Beispiel mit:

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

Wäre das Ergebnis int für variadic functionen im Beispiel ( siehe live ):

 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 

In Visual Studio können Sie FUNCSIG verwenden .

Update vor C ++ 11

Vor C ++ 11 wäre die Alternative für std :: initializer_list std :: vector oder einer der anderen Standardcontainer :

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

und die Alternative für variadische Vorlagen wären variadische functionen, obwohl sie nicht typsicher und allgemein errorsanfällig sind und die Verwendung unsicher sein kann, aber die einzige andere mögliche Alternative wäre die Verwendung von Standardargumenten , obwohl diese nur begrenzt verwendbar ist. Das folgende Beispiel ist eine modifizierte Version des Beispielcodes in der verknüpften Referenz:

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

Bei der Verwendung von Variadic-functionen gibt es auch Einschränkungen in den Argumenten, die Sie übergeben können, die im C ++ - Standardentwurf in Abschnitt 5.2.2 functionsaufruf Absatz 7 beschrieben sind :

Wenn für ein gegebenes Argument kein Parameter vorhanden ist, wird das Argument so übergeben, dass die empfangende function den Wert des Arguments durch Aufrufen von va_arg (18.7) erhalten kann. Die Standardkonvertierungen von lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) werden für den Argumentausdruck durchgeführt. Wenn nach diesen Konvertierungen das Argument keine Arithmetik, Aufzählung, pointers, pointers auf Member oder classntyp aufweist, ist das Programm schlecht formatiert. Wenn das Argument einen Nicht-POD-classntyp hat (Klausel 9), ist das Verhalten nicht definiert. [...]

C-Style Variadic-functionen werden in C ++ unterstützt.

Die meisten C ++ – Bibliotheken verwenden jedoch ein alternatives Idiom, z. B. während die 'c' printf function Variablenargumente verwendet, die das c++ cout Objekt < < überladen, welche Adresstypsicherheit und ADTs verwendet (vielleicht auf Kosten der Einfachheit der Implementierung).

In C ++ 11 gibt es eine Möglichkeit, Variablenargumentvorlagen zu erstellen, die zu einer wirklich eleganten und typsicheren Möglichkeit führen, variable Argumentfunktionen zu verwenden. Bjarne selbst gibt ein schönes Beispiel für printf mit Variablenargumentvorlagen in C ++ 11FAQ .

Persönlich halte ich das für so elegant, dass ich mich nicht einmal mit einer Variablenargumentfunktion in C ++ beschäftigen würde, solange dieser Compiler keine Unterstützung für C ++ 11-Variablenargumentvorlagen hat.

Abgesehen von Varargs oder Überladung können Sie Ihre Argumente in einem std :: vector oder anderen Containern (zB std :: map) aggregieren. Etwas wie das:

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

Auf diese Weise würden Sie Typsicherheit erlangen und die logische Bedeutung dieser Varianzargumente wäre offensichtlich.

Sicherlich kann dieser Ansatz performancesprobleme haben, aber Sie sollten sich nicht um sie kümmern, es sei denn, Sie sind sicher, dass Sie den Preis nicht bezahlen können. Es ist eine Art von “Pythonic” Ansatz zu C ++ …

In C ++ 11 können Sie tun:

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

Listeninitialisierer FTW!

Die einzige Möglichkeit besteht in der Verwendung von Variablen im C-Stil, wie hier beschrieben. Beachten Sie, dass dies keine empfohlene Vorgehensweise ist, da es nicht typsicher und errorsanfällig ist.

Es gibt keine Standard-C ++ – Möglichkeit, dies zu tun, ohne auf C-artige varargs zurückgreifen zu müssen ( ... ).

Es gibt natürlich Standardargumente, die abhängig vom Kontext wie eine variable Anzahl von Argumenten aussehen:

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

Alle vier functionsaufrufe rufen myfunc mit unterschiedlicher Anzahl von Argumenten auf. Wenn keine angegeben sind, werden die Standardargumente verwendet. Beachten Sie jedoch, dass Sie nur nachfolgende Argumente weglassen können. Es gibt keinen Weg, zum Beispiel i wegzulassen und nur j .

Es ist möglich, dass Sie Überladungs- oder Standardparameter verwenden möchten – definieren Sie dieselbe function mit voreingestellten Parametern:

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

Dadurch können Sie die Methode mit einem von vier verschiedenen Aufrufen aufrufen:

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

… oder Sie könnten nach den v_args Calling Conventions von C suchen.

Seit der Einführung variadischer Templates in C ++ 11 und falten von Ausdrücken in C ++ 17 ist es möglich, eine Template-function zu definieren, die an der aufgerufenen Stelle aufrufbar ist, als wäre sie eine Varidic-function, aber mit den Vorteilen :

  • stark typsicher sein;
  • arbeiten ohne die Laufzeitinformation der Anzahl der Argumente oder ohne die Verwendung eines “Stop” -Arguments.

Hier ist ein Beispiel für gemischte Argumenttypen

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

Und noch eine mit erzwungener Typübereinstimmung für alle Argumente:

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

Mehr Informationen:

  1. Variadic Templates, auch bekannt als Parameter Pack Parameter Pack (seit C ++ 11) - cppreference.com .
  2. Faltenausdrücke falten Ausdruck (seit C ++ 17) - cppreference.com .
  3. Siehe eine vollständige Programmdemonstration auf Coliru.

Wie andere gesagt haben, C-Stil varargs. Sie können aber auch etwas Ähnliches mit Standardargumenten machen.

Wenn Sie den Bereich der Anzahl der Argumente kennen, die zur Verfügung gestellt werden, können Sie immer eine function Überladung verwenden, wie

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

und so weiter…

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

Einfache Version

Wir könnten auch eine initialiser_list verwenden, wenn alle Argumente const und vom selben Typ sind