Variable Anzahl von Argumenten in C ++?

293

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

Nunos
quelle
49
Zu diesem Zeitpunkt mit C ++ 11 würden die Antworten auf diese Frage sehr unterschiedlich sein
K-Ballo
1
@ K-ballo Ich habe auch C ++ 11-Beispiele hinzugefügt, da kürzlich eine Frage dasselbe gestellt hat und ich der Meinung war, dass dies eine benötigt, um das Schließen zu rechtfertigen. Stackoverflow.com/questions/16337459/…
Shafik Yaghmour
1
Zu meiner Antwort wurden auch Optionen vor C ++ 11 hinzugefügt , sodass jetzt die meisten verfügbaren Optionen abgedeckt werden sollten.
Shafik Yaghmour
@ K-ballo es gibt afaik keine Möglichkeit, dies in C ++ zu tun, falls Sie einen erzwungenen Argumenttyp benötigen. Keine Konstruktion wie foo (int ... values): / Wenn Sie sich nicht für Typen interessieren, dann ja, verschiedene Vorlagen in C ++ 11 funktioniert
super

Antworten:

152

Sie sollten es wahrscheinlich nicht tun, und Sie können wahrscheinlich sicherer und einfacher tun, was Sie möchten. Technisch gesehen schließen Sie stdarg.h ein, um eine variable Anzahl von Argumenten in C zu verwenden. Daraus erhalten Sie den va_listTyp sowie drei Funktionen, die darauf ausgeführt werden va_start(), va_arg()und 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;
}

Wenn Sie mich fragen, ist das ein Chaos. Es sieht schlecht aus, ist unsicher und voller technischer Details, die nichts mit dem zu tun haben, was Sie konzeptionell erreichen möchten. Verwenden Sie stattdessen Überladung oder Vererbung / Polymorphismus, Builder-Muster (wie in operator<<()Streams) oder Standardargumente usw. Diese sind alle sicherer: Der Compiler erfährt mehr darüber, was Sie versuchen, sodass es mehr Gelegenheiten gibt, die er stoppen kann Sie, bevor Sie Ihr Bein abblasen.

wilhelmtell
quelle
7
Vermutlich können Sie keine Verweise auf eine varargs-Funktion übergeben, da der Compiler nicht weiß, wann er nach Wert und wann nach Referenz übergeben soll, und weil die zugrunde liegenden C-Makros nicht unbedingt wissen, was mit Verweisen zu tun ist - es gibt bereits Einschränkungen für was Sie können eine C-Funktion mit variablen Argumenten aufgrund von Beförderungsregeln übergeben.
Jonathan Leffler
3
Ist es notwendig, mindestens ein Argument vor der ...Syntax anzugeben ?
Lazer
3
@Lazer ist keine Sprach- oder Bibliotheksanforderung, aber die Standardbibliothek bietet Ihnen keine Möglichkeit, die Länge der Liste anzugeben. Der Anrufer muss Ihnen diese Informationen geben oder sie selbst herausfinden. Im Fall von printf()beispielsweise analysiert die Funktion das Zeichenfolgenargument für spezielle Token, um herauszufinden, wie viele zusätzliche Argumente in der Liste der variablen Argumente zu erwarten sind.
Wilhelmtell
11
Sie sollten wahrscheinlich <cstdarg>in C ++ anstelle von<stdarg.h>
newacct
11
Die variable Anzahl von Argumenten eignet sich hervorragend zum Debuggen oder für Funktionen / Methoden, die ein Array füllen. Es eignet sich auch hervorragend für viele mathematische Operationen wie Max, Min, Summe, Durchschnitt ... Es ist kein Chaos, wenn Sie sich nicht damit anlegen.
Tomáš Zato - Wiedereinsetzung Monica
395

In C ++ 11 haben Sie zwei neue Optionen, wie auf der Referenzseite für Variadic-Funktionen im Abschnitt Alternativen angegeben :

  • Variadische Vorlagen können auch verwendet werden, um Funktionen zu erstellen, die eine variable Anzahl von Argumenten annehmen. Sie sind häufig die bessere Wahl, da sie die Arten der Argumente nicht einschränken, keine Integral- und Gleitkomma-Promotions durchführen und typsicher sind. (seit C ++ 11)
  • Wenn alle Variablenargumente einen gemeinsamen Typ haben, bietet eine std :: initializer_list einen praktischen Mechanismus (wenn auch mit einer anderen Syntax) für den Zugriff auf Variablenargumente.

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

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

Wenn Sie die magische Variable PRETTY_FUNCTION verwenden gccoder clangwir sie verwenden können, um die Typensignatur der Funktion anzuzeigen, kann dies hilfreich sein, um zu verstehen, was vor sich geht. Zum Beispiel mit:

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

Die folgenden Ergebnisse würden für verschiedene Funktionen im Beispiel folgen ( siehe live ):

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 können Sie FUNCSIG verwenden .

Aktualisieren Sie Pre C ++ 11

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

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

und die Alternative für variadische Vorlagen wären variadische Funktionen, obwohl sie nicht typsicher und im Allgemeinen fehleranfällig sind und unsicher zu verwenden sind, aber die einzige andere mögliche Alternative wäre die Verwendung von Standardargumenten , obwohl dies nur eine begrenzte Verwendung hat. Das folgende Beispiel ist eine modifizierte Version des Beispielcodes in der verknüpften Referenz:

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

Die Verwendung variabler Funktionen unterliegt auch Einschränkungen bei den Argumenten, die Sie übergeben können. Dies wird im Entwurf des C ++ - Standards im Abschnitt 5.2.2 Funktionsaufruf Absatz 7 beschrieben :

Wenn für ein bestimmtes Argument kein Parameter vorhanden ist, wird das Argument so übergeben, dass die empfangende Funktion den Wert des Arguments durch Aufrufen von va_arg (18.7) ermitteln kann. Die Standardkonvertierungen lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) werden für den Argumentausdruck ausgeführt. Wenn das Argument nach diesen Konvertierungen keine Arithmetik, Aufzählung, keinen Zeiger, keinen Zeiger auf ein Mitglied oder keinen Klassentyp enthält, ist das Programm fehlerhaft. Wenn das Argument einen Nicht-POD-Klassentyp hat (Klausel 9), ist das Verhalten undefiniert. [...]

Shafik Yaghmour
quelle
Ist Ihre typenamevs- classNutzung über absichtlich? Wenn ja, bitte erläutern.
Kevinarpe
1
@ kevinarpe nicht beabsichtigt, es ändert aber nichts.
Shafik Yaghmour
Ihr erster Link sollte wahrscheinlich stattdessen zu en.cppreference.com/w/cpp/language/variadic_arguments sein .
Alexey Romanov
Ist es möglich, eine Funktion initializer_listrekursiv zu machen ?
idclev 463035818
33

Eine C ++ 17-Lösung: vollständige Typensicherheit + nette Aufrufsyntax

Seit der Einführung variabler Vorlagen in C ++ 11 und Fold-Ausdrücken in C ++ 17 ist es möglich, eine Vorlagenfunktion zu definieren, die am Aufruferstandort aufrufbar ist, als wäre sie eine varidische Funktion, aber mit den Vorteilen von ::

  • stark typsicher sein;
  • Arbeiten Sie ohne die Laufzeitinformationen der Anzahl der Argumente oder ohne die Verwendung eines "Stop" -Arguments.

Hier ist ein Beispiel für gemischte Argumenttypen

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

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

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

Mehr Informationen:

  1. Variadische Vorlagen, auch als Parameterpaket bekannt Parameterpaket (seit C ++ 11) - cppreference.com .
  2. Fold-Ausdrücke Fold-Ausdruck (seit C ++ 17) - cppreference.com .
  3. Sehen Sie sich eine vollständige Programmdemonstration zu coliru an.
YSC
quelle
13
Eines Tages hoffe ich, dass ich lesen kanntemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian
1
@Eladian las es als "Dieses Ding ist nur aktiviert, wenn Headund Tail... sind gleich ", wobei " sind gleich " bedeutet std::conjunction<std::is_same<Head, Tail>...>. Lesen Sie diese letzte Definition als " Headist das gleiche wie alle Tail...".
YSC
24

In C ++ 11 können Sie Folgendes tun:

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

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

Listeninitialisierer FTW!

Markus Zancolò
quelle
19

In C ++ 11 gibt es eine Möglichkeit, Vorlagen für variable Argumente zu erstellen, die zu einer wirklich eleganten und typsicheren Möglichkeit führen, Funktionen für variable Argumente zu verwenden. Bjarne selbst gibt ein schönes Beispiel für printf unter Verwendung von Vorlagen mit variablen Argumenten in C ++ 11FAQ .

Persönlich halte ich dies für so elegant, dass ich mich nicht einmal mit einer variablen Argumentfunktion in C ++ beschäftigen würde, bis dieser Compiler C ++ 11-Vorlagen für variable Argumente unterstützt.

Allgegenwärtig
quelle
@donlan - Wenn Sie C ++ 17 verwenden, können Sie Fold-Ausdrücke verwenden, um die Dinge in einigen Fällen viel einfacher zu machen (denken Sie hier kreativ, Sie können den ,Operator mit Fold-Ausdrücken verwenden). Ansonsten denke ich nicht.
Omnifarious
15

Variadische Funktionen im C-Stil werden in C ++ unterstützt.

Die meisten C ++ - Bibliotheken verwenden jedoch eine alternative Redewendung, z. B. während die 'c' printfFunktion variable Argumente verwendet, verwendet das c++ coutObjekt eine <<Überladung, die sich mit Typensicherheit und ADTs befasst (möglicherweise auf Kosten der einfachen Implementierung).

Wille
quelle
Dies scheint auch nur bei einer Funktion wie dem Drucken zu funktionieren, bei der Sie tatsächlich eine Iteration einer einzelnen Argumentfunktion über jedes Argument hinweg haben. Andernfalls initialisieren Sie nur eine Liste und geben die Liste für das Ende weiter std::initializer_lists... Und dies führt bereits zu einer massiven Komplexität einer einfachen Aufgabe.
Christopher
13

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

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

Auf diese Weise erhalten Sie Typensicherheit und die logische Bedeutung dieser variadischen Argumente wird offensichtlich.

Sicherlich kann dieser Ansatz Leistungsprobleme haben, aber Sie sollten sich darüber keine Sorgen machen, es sei denn, Sie sind sicher, dass Sie den Preis nicht zahlen können. Es ist eine Art "pythonischer" Ansatz für c ++ ...

Francesco
quelle
6
Reiniger wäre es, keine Vektoren durchzusetzen. Verwenden Sie stattdessen ein Vorlagenargument, das die Sammlung im STL-Stil angibt, und durchlaufen Sie es dann mit den Start- und Endmethoden des Arguments. Auf diese Weise können Sie std :: vector <T>, das std :: array <T, N> von c ++ 11, std :: initializer_list <T> verwenden oder sogar Ihre eigene Sammlung erstellen.
Jens Åkerblom
3
@ JensÅkerblom Ich stimme zu, aber dies ist die Art von Wahl, die für das jeweilige Problem analysiert werden sollte, um ein Über-Engineering zu vermeiden. Da dies eine Frage der API-Signatur ist, ist es wichtig, den Kompromiss zwischen maximaler Flexibilität und Klarheit der Absicht / Benutzerfreundlichkeit / Wartbarkeit usw. zu verstehen.
Francesco
8

Der einzige Weg ist die Verwendung von Variablenargumenten im C-Stil, wie hier beschrieben . Beachten Sie, dass dies keine empfohlene Vorgehensweise ist, da sie nicht typsicher und fehleranfällig ist.

Dave Van den Eynde
quelle
Mit fehleranfällig meine ich, dass Sie möglicherweise sehr, sehr gefährlich meinen? Besonders wenn Sie mit nicht vertrauenswürdigen Eingaben arbeiten.
Kevin Loney
Ja, aber wegen der Typensicherheitsprobleme. Denken Sie an alle möglichen Probleme, die reguläres printf hat: Formatzeichenfolgen, die nicht mit übergebenen Argumenten übereinstimmen, und so weiter. printf verwendet die gleiche Technik, übrigens.
Dave Van den Eynde
7

Es gibt keine Standardmethode für C ++, ohne auf varargs ( ...) im C-Stil zurückzugreifen .

Es gibt natürlich Standardargumente, die je nach 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 Funktionsaufrufe werden myfuncmit unterschiedlicher Anzahl von Argumenten aufgerufen . Wenn keine angegeben sind, werden die Standardargumente verwendet. Beachten Sie jedoch, dass Sie nur nachfolgende Argumente weglassen können. Es gibt zum Beispiel keine Möglichkeit, inur wegzulassen und zu geben j.

Zoli
quelle
4

Möglicherweise möchten Sie Überladung oder Standardparameter - definieren Sie dieselbe Funktion mit Standardparametern:

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

Auf diese Weise 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 suchen nach den v_args-Aufrufkonventionen von C.

Kieveli
quelle
2

Wenn Sie den Bereich der Anzahl der Argumente kennen, die bereitgestellt werden, können Sie immer eine Funktionsüberladung verwenden, z

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

und so weiter...

Dunya Degirmenci
quelle
2

Beispiel für die Verwendung verschiedener Vorlagen zur Reproduktion console.login JavaScript:

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

Dateiname zB 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
quelle
1

Wie andere gesagt haben, Varargs im C-Stil. Ähnliches können Sie aber auch mit Standardargumenten tun.

Thomas Padron-McCarthy
quelle
Könnten Sie näher darauf eingehen?
Fund Monica Klage
@QPaysTaxes: Ein Beispiel für die Verwendung von Standardargumenten finden Sie in Zoltans Antwort.
Thomas Padron-McCarthy
0

Es ist jetzt möglich ... mit boost any und templates In diesem Fall kann der Argumenttyp gemischt werden

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

Kombinieren Sie die C- und C ++ - Lösungen für die semantisch einfachste, performanteste und dynamischste Option. Wenn Sie es vermasseln, versuchen Sie etwas anderes.

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

Benutzer schreibt:

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

Semantisch sieht und fühlt sich dies genau wie eine n-Argument-Funktion an. Unter der Haube können Sie es auf die eine oder andere Weise auspacken.

Christopher
quelle
-1

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

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

Einfache Version

Вениамин Раскин
quelle
1
Und undefiniertes Verhalten, das auf nichts anderem als x86 funktioniert.
SS Anne