web-dev-qa-db-fra.com

Nombre variable d'arguments en C ++?

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

246
nunos

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 devez inclure stdarg.h. À partir de là, vous obtiendrez le type va_list ainsi que trois fonctions appelées 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 semble mauvais, dangereux et plein de détails techniques qui n’ont rien à voir avec ce que vous essayez conceptuellement d’atteindre. Au lieu de cela, envisagez d'utiliser la surcharge ou l'héritage/polymorphisme, le modèle de générateur (comme dans operator<<() dans les flux) ou les arguments par défaut, etc. Tout cela est plus sûr: le compilateur en apprend plus sur ce que vous essayez de faire. plusieurs fois, il peut vous arrêter avant que vous vous déchaussiez la jambe.

143
wilhelmtell

Dans C++ 11 , vous avez deux nouvelles options: la page de référence Fonctions variadiques dans la section Alternatives États:

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

Ci-dessous un exemple montrant les deux alternatives (le 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 gcc ou clang, nous pouvons utiliser la magie PRETTY_FUNCTION variable 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 <<std::endl ;

aurait pour résultat les résultats suivants pour les fonctions variadiques dans l'exemple (le 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 .

Mise à jour de Pre C++ 11

Pre 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 variadiques serait fonctions variadiques bien qu'ils ne soient pas type-safe et dans general sujet aux erreurs et pouvant être dangereux à utiliser mais la seule autre alternative possible serait d'utiliser les arguments par défaut , bien que leur utilisation soit 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 des fonctions variadiques est également assortie de restrictions dans les arguments que vous pouvez transmettre, détaillées 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 manière à ce que la fonction réceptrice puisse obtenir la valeur de l'argument en appelant va_arg (18.7). Les conversions standard de lvalue à rvalue (4.1), de tableau à pointeur (4.2) et de 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 membre ou de type de classe, le programme est mal formé. Si l'argument a un type de classe autre que POD (clause 9), le comportement est indéfini. [...]

349
Shafik Yaghmour

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!

19
Markus Zancolò

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

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

17
Omnifarious

Les fonctions variadiques de style C sont supportées en C++.

Cependant, la plupart des bibliothèques C++ utilisent un autre idiome, par exemple. alors que la fonction 'c' printf prend des arguments variables, l'objet c++ cout utilise la surcharge << qui traite de la sécurité de type et des ADT (peut-être au détriment de la simplicité d'implémentation).

15
Will

En plus des 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 <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 la sécurité de type et la signification logique de ces arguments variadic serait évidente.

Certes, cette approche peut entraîner des problèmes de performances, mais vous ne devriez pas vous en inquiéter à moins d’être sûr de ne pas pouvoir en payer le prix. C'est une sorte d'approche "pythonique" de c ++ ...

13
Francesco

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

Depuis l'introduction des modèles variadiques dans C++ 11 et des expressions de pliage dans C++ 17, il est possible de définir une fonction modèle qui, sur le site appelé, est appelable comme s'il s'agissait d'une fonction varidique mais présentant les avantages de :

  • être fortement en sécurité;
  • 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 mélangés

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

Et un autre avec correspondance de type appliqué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 variés, également connus sous le nom de paquet de paramètres Pack de paramètres (depuis C++ 11) - cppreference.com .
  2. Expressions de plis expression de plis (depuis C++ 17) - cppreference.com .
  3. Voir un démonstration du programme complet sur coliru.
12
YSC

Le seul moyen est d'utiliser des arguments de variable de style C, comme décrit ici . Notez que ce n’est pas une pratique recommandée, car il n’est ni typé, ni sujet aux erreurs.

8

Il n’existe pas de méthode C++ standard pour le faire sans recourir à des varargs de style C (...).

Il y a bien sûr des arguments par défaut qui "ressemblent" en quelque sorte à 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 d'arguments variable. 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. Par exemple, il n’ya aucun moyen d’omettre i et de ne donner que j.

7
Zoli

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 suivants:

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.

4
Kieveli

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 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...

2
Dunya Degirmenci
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

Utilisation de modèles variadiques, exemple pour reproduire console.log comme 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;
    }
};
1
lama12345

C'est possible maintenant ... en utilisant boost any et les templates. 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);
}
0
Aftershock

Nous pourrions aussi utiliser une liste d'initialisation si tous les arguments sont constants et du même type

0
pkumar0