Nombre variable d’arguments en C ++?

Comment puis-je écrire une fonction qui accepte un nombre variable d’arguments? Est-ce possible, comment?

    Vous ne devriez probablement pas le faire et vous pouvez probablement faire ce que vous voulez de manière plus sûre et plus simple. Techniquement, pour utiliser le nombre variable d’arguments dans C, vous incluez stdarg.h. A partir de cela, vous obtiendrez le type va_list ainsi que trois fonctions appelées va_start() , va_arg() et 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; } 

    Si vous me demandez, c’est un désordre. Ça a l’air mauvais, c’est dangereux et c’est plein de détails techniques qui n’ont rien à voir avec ce que vous essayez de réaliser. Au lieu de cela, envisagez d’utiliser la surcharge ou l’inheritance / le polymorphism, le modèle de générateur (comme dans operator< <() dans les stream) ou les arguments par défaut. Tous sont plus sûrs: le compilateur en sait plus sur ce que vous essayez de faire de plus en plus d'occasions peuvent vous arrêter avant de vous casser la jambe.

    En C ++ 11, vous avez deux nouvelles options, comme l’ indique la page de référence des fonctions Variadic dans la section Alternatives :

    • Les modèles de variables peuvent également être utilisés pour créer des fonctions qui prennent un nombre variable d’arguments. Ils sont souvent le meilleur choix car ils n’imposent pas de ressortingctions sur les types d’arguments, n’effectuent pas de promotions intégrales et à virgule flottante et sont de type sécurisé. (depuis C ++ 11)
    • Si tous les arguments de la variable partagent un type commun, un std :: initializer_list fournit un mécanisme pratique (avec une syntaxe différente) pour accéder aux arguments variables.

    Vous trouverez ci-dessous un exemple montrant les deux alternatives ( voir en direct ):

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

    Si vous utilisez gcc ou clang vous pouvez utiliser la variable magique PRETTY_FUNCTION pour afficher la signature de type de la fonction, ce qui peut être utile pour comprendre ce qui se passe. Par exemple en utilisant:

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

    les résultats int suivraient-ils pour les fonctions variadiques dans l'exemple ( voyez-le en direct ):

     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_ssortingng]: Hello 

    Dans Visual Studio, vous pouvez utiliser FUNCSIG .

    Mettre à jour Pre C ++ 11

    Pre C ++ 11 l'alternative pour std :: initializer_list serait std :: vector ou l'un des autres conteneurs standard :

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

    et l'alternative pour les modèles variadiques serait des fonctions variadiques bien qu'elles ne soient pas sûres et généralement sujettes aux erreurs et qu'elles puissent être dangereuses à utiliser, mais la seule autre alternative potentielle serait d'utiliser des arguments par défaut , bien que leur utilisation soit limitée. L'exemple ci-dessous est une version modifiée du code exemple dans la référence liée:

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

    L'utilisation de fonctions variadiques s'accompagne également de ressortingctions dans les arguments que vous pouvez passer, détaillés dans le projet de norme C ++ à la section 5.2.2 Appel de fonction, paragraphe 7 :

    Lorsqu'il n'y a pas de paramètre pour un argument donné, l'argument est transmis de telle manière que la fonction récepsortingce puisse obtenir la valeur de l'argument en appelant va_arg (18.7). Les conversions standard lvalue-à-rvalue (4.1), tableau-à-pointeur (4.2) et fonction-pointeur (4.3) sont effectuées sur l'expression de l'argument. Après ces conversions, si l'argument n'a pas d'arithmétique, d'énumération, de pointeur, de pointeur sur membre ou de type de classe, le programme est mal formé. Si l'argument a un type de classe non-POD (clause 9), le comportement est indéfini. [...]

    Les fonctions variadiques de style C sont supscopes en C ++.

    Cependant, la plupart des bibliothèques C ++ utilisent un idiome alternatif, par exemple, alors que la fonction 'c' printf prend des arguments variables. L’object c++ cout utilise une surcharge qui traite de la sécurité des types et des ADT.

    En C ++ 11, il existe un moyen de créer des modèles d’argument de variables qui conduisent à un moyen très élégant et sûr de disposer de fonctions d’argument variables. Bjarne lui-même donne un bel exemple de printf en utilisant des modèles d’arguments variables dans le C ++ 11FAQ .

    Personnellement, je trouve cela si élégant que je ne me soucierais même pas d’une fonction d’argument variable en C ++ tant que ce compilateur n’aura pas pris en charge les modèles d’arguments variables C ++ 11.

    En dehors de varargs ou de la surcharge, vous pouvez envisager d’agréger vos arguments dans un std :: vector ou d’autres conteneurs (std :: map par exemple). Quelque chose comme ça:

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

    De cette manière, vous gagneriez en sécurité de type et la signification logique de ces arguments variadiques serait évidente.

    Cette approche peut sûrement avoir des problèmes de performance, mais vous ne devez pas vous en soucier, sauf si vous êtes certain de ne pas pouvoir en payer le prix. C’est une sorte d’approche “pythonique” du c ++ …

    en c ++ 11 vous pouvez faire:

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

    Initialiseur de liste FTW!

    Le seul moyen consiste à utiliser des arguments variables de style C, comme décrit ici . Notez qu’il ne s’agit pas d’une pratique recommandée, car elle n’est pas sûre pour les types et les erreurs.

    Il n’y a pas de moyen C ++ standard pour faire cela sans avoir recours à varargs de style C ( ... ).

    Il y a bien sûr des arguments par défaut qui ressemblent à un nombre variable d’arguments en fonction du contexte:

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

    Les quatre appels de fonction appellent myfunc avec un nombre variable d’arguments. Si aucun n’est donné, les arguments par défaut sont utilisés. Notez cependant que vous ne pouvez omettre que des arguments de fin. Par exemple, il n’y a aucun moyen d’omettre et de ne donner que j .

    Il est possible que vous souhaitiez une surcharge ou des parameters par défaut – définissez la même fonction avec les parameters par défaut:

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

    Cela vous permettra d’appeler la méthode avec l’un des quatre appels différents:

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

    … ou vous pourriez chercher les conventions d’appel v_args de C.

    Depuis l’introduction de modèles variadiques dans les expressions C ++ 11 et fold en C ++ 17, il est possible de définir une fonction template qui, sur le site de l’appelé, peut être appelée comme une fonction varidic mais avec les avantages suivants: :

    • être fortement de type sûr;
    • travailler sans les informations d’exécution du nombre d’arguments, ou sans l’utilisation d’un argument “stop”.

    Voici un exemple pour les types d’arguments mixtes

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

    Et un autre avec une correspondance de type imposée pour tous les arguments:

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

    Plus d'information:

    1. Modèles de variables, également appelés pack de parameters Pack de parameters (depuis C ++ 11) - cppreference.com .
    2. Pliez les expressions pliez l'expression (depuis C ++ 17) - cppreference.com .
    3. Voir une démonstration complète du programme sur coliru.

    Comme d’autres l’ont dit, varargs de style C. Mais vous pouvez également faire quelque chose de similaire avec les arguments par défaut.

    Si vous connaissez le nombre d’arguments à fournir, vous pouvez toujours utiliser une surcharge de fonctions, comme

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

    etc…

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

    Version simple

    Nous pourrions également utiliser une liste initializer_list si tous les arguments sont const et du même type