Nombre d'arguments variable en C ++?

293

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

nunos
la source
49
En ce moment avec C ++ 11, les réponses à cette question seraient très différentes
K-ballo
1
@ K-ballo J'ai également ajouté des exemples C ++ 11 car une question récente a posé la même chose récemment et j'ai senti que cela en avait besoin pour justifier sa fermeture stackoverflow.com/questions/16337459/…
Shafik Yaghmour
1
Ajout d' options pré C ++ 11 à ma réponse également, donc il devrait maintenant couvrir la plupart des choix disponibles.
Shafik Yaghmour
@ K-ballo il n'y a afaik aucun moyen de le faire en C ++ au cas où vous auriez besoin d'un type d'argument forcé. en C ++ 11 fonctionne très bien
graywolf

Réponses:

152

Vous ne devriez probablement pas, et vous pouvez probablement faire ce que vous voulez faire d'une manière plus sûre et plus simple. Techniquement, pour utiliser un nombre variable d'arguments en C, vous incluez stdarg.h. De là, vous obtiendrez le va_listtype ainsi que trois fonctions qui opèrent sur lui appelé va_start(), va_arg()et 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;
}

Si vous me demandez, c'est un gâchis. Cela a l'air mauvais, ce n'est pas sûr, et c'est plein de détails techniques qui n'ont rien à voir avec ce que vous essayez conceptuellement d'accomplir. Au lieu de cela, envisagez d'utiliser la surcharge ou l'héritage / polymorphisme, le modèle de générateur (comme dans les operator<<()flux) ou les arguments par défaut, etc. Ceux-ci sont tous plus sûrs: le compilateur en sait plus sur ce que vous essayez de faire, donc il y a plus d'occasions qu'il peut s'arrêter vous avant de vous couper la jambe.

wilhelmtell
la source
7
Vraisemblablement, vous ne pouvez pas passer de références à une fonction varargs parce que le compilateur ne saurait pas quand passer par valeur et quand par référence, et parce que les macros C sous-jacentes ne sauraient pas nécessairement quoi faire avec les références - il y a déjà des restrictions sur ce vous pouvez passer dans une fonction C avec des arguments variables à cause de choses comme les règles de promotion.
Jonathan Leffler
3
est-il nécessaire de fournir au moins un argument avant la ...syntaxe?
Lazer
3
@Lazer ce n'est pas une exigence de langue ou de bibliothèque, mais la bibliothèque standard ne vous donne pas les moyens de dire la longueur de la liste. Vous avez besoin de l'appelant pour vous donner ces informations ou bien le découvrir vous-même. Dans le cas de printf(), par exemple, la fonction analyse l'argument chaîne pour les jetons spéciaux pour déterminer le nombre d'arguments supplémentaires qu'elle devrait attendre dans la liste des arguments variables.
wilhelmtell
11
vous devriez probablement utiliser <cstdarg>en C ++ au lieu de<stdarg.h>
newacct
11
Le nombre variable d'arguments est idéal pour le débogage ou pour les fonctions / méthodes qui remplissent un tableau. De plus, il est idéal pour de nombreuses opérations mathématiques, telles que max, min, sum, average ... Ce n'est pas un gâchis lorsque vous ne le gâtez pas.
Tomáš Zato - Rétablir Monica
395

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

  • Les modèles variadiques 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 restrictions sur les types d'arguments, n'effectuent pas de promotions intégrales et à virgule flottante et sont sûrs pour les types. (depuis C ++ 11)
  • Si tous les arguments variables partagent un type commun, une liste std :: initializer_list fournit un mécanisme pratique (mais avec une syntaxe différente) pour accéder aux arguments variables.

Voici un exemple montrant les deux alternatives ( voir en direct ):

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

Si vous utilisez gccou clangnous pouvons utiliser la variable magique PRETTY_FUNCTION pour afficher la signature de type de la fonction qui peut être utile pour comprendre ce qui se passe. Par exemple en utilisant:

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

donnerait les résultats suivants pour les fonctions variadiques dans l'exemple ( voir en direct ):

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

Dans Visual Studio, vous pouvez utiliser FUNCSIG .

Mettre à jour Pre C ++ 11

Avant C ++ 11, l'alternative pour std :: initializer_list serait std :: vector ou l'un des autres conteneurs 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 ) ;
}

et l'alternative pour les modèles variadic serait des fonctions variadic bien qu'elles ne soient pas sécuritaires pour le type et en général sujettes aux erreurs et puissent être dangereuses à utiliser, mais la seule autre alternative potentielle serait d'utiliser des arguments par défaut , bien que cela ait une utilisation limitée. L'exemple ci-dessous est une version modifiée de l'exemple de code dans la référence liée:

#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'utilisation de fonctions variadiques s'accompagne également de restrictions dans les arguments que vous pouvez transmettre, qui sont détaillés dans le projet de norme C ++ dans 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 passé de telle manière que la fonction de réception 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 d'argument. Après ces conversions, si l'argument n'a pas d'arithmétique, d'énumération, de pointeur, de pointeur sur un membre ou un type de classe, le programme est mal formé. Si l'argument a un type de classe non POD (article 9), le comportement n'est pas défini. [...]

Shafik Yaghmour
la source
Votre utilisation typenamevs est class-elle intentionnelle ci-dessus? Si oui, veuillez expliquer.
kevinarpe
1
@kevinarpe n'est pas intentionnel, cela ne change rien cependant.
Shafik Yaghmour
Votre premier lien devrait probablement être à la place en.cppreference.com/w/cpp/language/variadic_arguments .
Alexey Romanov
est-il possible de faire une fonction prenant une initializer_listrécursive?
idclev 463035818
33

Une solution C ++ 17: sécurité complète + belle syntaxe d'appel

Depuis l'introduction des modèles variadiques en C ++ 11 et des expressions de repli en C ++ 17, il est possible de définir une fonction de modèle qui, sur le site de l'appelant, peut être appelée comme s'il s'agissait d'une fonction varidique mais avec les avantages de :

  • être fortement sûr de type;
  • 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<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

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

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

Plus d'information:

  1. Modèles Variadic, également connus sous le nom de pack de paramètres Pack de paramètres (depuis C ++ 11) - cppreference.com .
  2. Expressions de pli expression de pli (depuis C ++ 17) - cppreference.com .
  3. Voir une démonstration complète du programme sur coliru.
YSC
la source
13
un jour, j'espère mal liretemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian
1
@Eladian l'a lu comme "Cette chose n'est activée que si Headet Tail... sont les mêmes ", où " sont les mêmes " signifie std::conjunction<std::is_same<Head, Tail>...>. Lisez cette dernière définition comme " Headest la même chose que tout Tail...".
YSC
24

en c ++ 11 vous pouvez faire:

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

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

liste initialiseur FTW!

Markus Zancolò
la source
19

En C ++ 11, il existe un moyen de faire des modèles d'arguments variables qui conduisent à un moyen vraiment élégant et sûr d'avoir des fonctions d'arguments variables. Bjarne lui-même donne un bel exemple de printf utilisant des modèles d'arguments variables dans le C ++ 11FAQ .

Personnellement, je considère cela si élégant que je ne m'embêterais même pas avec une fonction d'argument variable en C ++ jusqu'à ce que ce compilateur prenne en charge les modèles d'argument variable C ++ 11.

Très varié
la source
@donlan - Si vous utilisez C ++ 17, vous pouvez utiliser des expressions de pliage pour simplifier les choses dans certains cas (pensez créativement ici, vous pouvez utiliser l' ,opérateur avec des expressions de pli). Sinon, je ne pense pas.
Omnifarious le
15

Les fonctions variadiques de style C sont prises en charge en C ++.

Cependant, la plupart des bibliothèques C ++ utilisent un idiome alternatif, par exemple, alors que la 'c' printffonction prend des arguments variables, l' c++ coutobjet utilise une <<surcharge qui traite de la sécurité des types et des ADT (peut-être au détriment de la simplicité de mise en œuvre).

Volonté
la source
En outre, cela ne semble fonctionner que dans le cas d'une fonction comme l'impression, où vous avez en fait une itération d'une fonction d'argument unique sur chaque argument. Sinon, vous êtes juste en train d'initialiser une liste et de la passer pour la fin std::initializer_lists... Et cela introduit déjà une complexité énorme sur une tâche simple.
Christopher
13

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

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

De cette façon, vous gagneriez en sécurité de type et la signification logique de ces arguments variadiques serait évidente.

Certes, cette approche peut avoir des problèmes de performances, mais vous ne devriez pas vous en préoccuper à moins d'être sûr que vous ne pouvez pas en payer le prix. C'est une sorte d'approche "Pythonique" du c ++ ...

Francesco
la source
6
Plus propre serait de ne pas appliquer les vecteurs. Utilisez plutôt un argument de modèle spécifiant la collection de style STL, puis parcourez-le à l'aide des méthodes de début et de fin de l'argument. De cette façon, vous pouvez utiliser std :: vector <T>, std :: array <T, N> de c ++ 11, std :: initializer_list <T> ou même créer votre propre collection.
Jens Åkerblom
3
@ JensÅkerblom Je suis d'accord mais c'est le genre de choix qui doit être analysé pour le problème en question, afin d'éviter une ingénierie excessive. Puisqu'il s'agit d'une signature API, il est important de comprendre le compromis entre flexibilité maximale et clarté d'intention / utilisabilité / maintenabilité, etc.
Francesco
8

La seule façon consiste à utiliser des arguments de variable de style C, comme décrit ici . Notez que ce n'est pas une pratique recommandée, car elle n'est pas sûre pour les types et sujette aux erreurs.

Dave Van den Eynde
la source
Par erreur, je suppose que vous entendez potentiellement très très dangereux? Surtout lorsque vous travaillez avec une entrée non fiable.
Kevin Loney
Oui, mais à cause des problèmes de sécurité de type. Pensez à tous les problèmes possibles rencontrés par printf: les chaînes de format ne correspondent pas aux arguments passés, etc. printf utilise la même technique, BTW.
Dave Van den Eynde,
7

Il n'y a aucun moyen C ++ standard de le faire sans recourir à varargs ( ...) de style C.

Il y a bien sûr des arguments par défaut qui "ressemblent" à un nombre variable d'arguments selon le 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 myfuncavec un nombre variable d'arguments. Si aucun n'est fourni, les arguments par défaut sont utilisés. Notez cependant que vous ne pouvez omettre que les arguments de fin. Il n'y a aucun moyen, par exemple d'omettre iet de donner uniquement j.

Zoli
la source
4

Il est possible que vous souhaitiez une surcharge ou des paramètres par défaut - définissez la même fonction avec les paramètres 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 rechercher les conventions d'appel v_args de C.

Kieveli
la source
2

Si vous connaissez la plage de nombre d'arguments qui seront fournis, vous pouvez toujours utiliser une surcharge de fonction, comme

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

etc...

Dunya Degirmenci
la source
2

Utilisation de modèles variadiques, exemple pour reproduire console.logcomme vu en JavaScript:

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

Nom de fichier, par exemple 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
la source
1

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

Thomas Padron-McCarthy
la source
Pourriez-vous élaborer?
Fund Monica's Lawsuit
@QPaysTaxes: Pour un exemple de la façon de le faire avec des arguments par défaut, regardez la réponse de Zoltan.
Thomas Padron-McCarthy
0

C'est possible maintenant ... en utilisant boost any et des modèles Dans ce cas, le type d'arguments peut être mélangé

#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);
}
Réplique
la source
0

Combinez les solutions C et C ++ pour l'option sémantiquement la plus simple, la plus performante et la plus dynamique. Si vous vous trompez, essayez autre chose.

// 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'utilisateur écrit:

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

Sémantiquement, cela ressemble et se sent exactement comme une fonction à n arguments. Sous le capot, vous pourriez le déballer dans un sens ou dans l'autre.

Christophe
la source
-1

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

pkumar0
la source
-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;
   }
}

Version simple

Вениамин Раскин
la source
1
Et un comportement indéfini qui ne fonctionnera pas sur autre chose que x86.
SS Anne