web-dev-qa-db-fra.com

Nombre variable de paramètres en fonction en C ++

Comment je peux avoir un nombre variable de paramètres dans ma fonction en C++.

Analogique en C #:

public void Foo(params int[] a) {
    for (int i = 0; i < a.Length; i++)
        Console.WriteLine(a[i]);
}

public void UseFoo() {
    Foo();
    Foo(1);
    Foo(1, 2);
}

Analogique en Java:

public void Foo(int... a) {
    for (int i = 0; i < a.length; i++)
        System.out.println(a[i]);
}

public void UseFoo() {
    Foo();
    Foo(1);
    Foo(2);
}
31
AndreyAkinshin

Celles-ci sont appelées fonctions variadiques . Listes Wikipédia exemple de code pour C++ .

Pour implémenter de manière portative des fonctions variadiques dans le langage de programmation C, le fichier d'en-tête standard stdarg.h doit être utilisé. L'ancien en-tête varargs.h a été déconseillé au profit de stdarg.h. En C++, le fichier d'en-tête cstdarg doit être utilisé.

Pour créer une fonction variadique, des points de suspension (...) Doivent être placés à la fin d'une liste de paramètres. A l'intérieur du corps de la fonction, une variable de type va_list Doit être définie. Ensuite, les macros va_start(va_list, last fixed param), va_arg(va_list, cast type), va_end(va_list) peuvent être utilisées. Par exemple:

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}
43
Stephan202

La vraie solution C++ est constituée de modèles variadiques. Vous aurez besoin d'un compilateur assez récent et activez le support C++ 11 si nécessaire.

Deux façons de gérer le problème "faire la même chose avec tous les arguments de fonction": récursivement, et avec une solution laide (mais très très conforme aux normes).

La solution récursive ressemble un peu à ceci:

template<typename... ArgTypes>
void print(ArgTypes... args);
template<typename T, typename... ArgTypes>
void print(T t, ArgTypes... args)
{
  std::cout << t;
  print(args...);
}
template<> void print() {} // end recursion

Il génère un symbole pour chaque collection d'arguments, puis un pour chaque étape de la récursivité. C'est pour le moins sous-optimal, donc les gens C++ impressionnants ici à SO ont pensé à ne super astuce abusant de l'effet secondaire d'une initialisation de liste :

struct expand_type {
  template<typename... T>
  expand_type(T&&...) {}
};
template<typename... ArgTypes>
void print(ArgTypes... args)
{ 
  expand_type{ 0, (std::cout << args, 0)... };
}

Le code n'est pas généré pour un million d'instanciations de modèles légèrement différentes, et en bonus, vous obtenez l'ordre préservé de vos arguments de fonction. Voir l'autre réponse pour les détails de cette solution.

15
rubenvb

Dans C++ 11 et versions ultérieures, vous pouvez également utiliser des listes d'initialisation.

int sum(const initializer_list<int> &il)
{
    int nSum = 0;
    for (auto x: il) 
        nSum += x;
    return nsum;
}

cout << sum( { 3, 4, 6, 9 } );
6
Seb

Mis à part les autres réponses, si vous essayez simplement de passer un tableau d'entiers, pourquoi pas:

void func(const std::vector<int>& p)
{
    // ...
}

std::vector<int> params;
params.Push_back(1);
params.Push_back(2);
params.Push_back(3);

func(params);

Cependant, vous ne pouvez pas l'appeler sous forme de paramètre. Vous devez utiliser l'une des fonctions variadiques répertoriées dans vos réponses. C++ 0x autorisera les modèles variadiques, ce qui le rendra sûr pour le type, mais pour l'instant c'est essentiellement de la mémoire et du casting.

Vous pouvez émuler une sorte de paramètre variadique-> chose vectorielle:

// would also want to allow specifying the allocator, for completeness
template <typename T> 
std::vector<T> gen_vec(void)
{
    std::vector<T> result(0);
    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1)
{
    std::vector<T> result(1);

    result.Push_back(a1);

    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1, T a2)
{
    std::vector<T> result(1);

    result.Push_back(a1);
    result.Push_back(a2);

    return result;
}

template <typename T> 
std::vector<T> gen_vec(T a1, T a2, T a3)
{
    std::vector<T> result(1);

    result.Push_back(a1);
    result.Push_back(a2);
    result.Push_back(a3);

    return result;
}

// and so on, boost stops at nine by default for their variadic templates

Usage:

func(gen_vec(1,2,3));
5
GManNickG

Voir Fonctions variadiques en C, Objective-C, C++ et D

Vous devez inclure stdarg.h puis utiliser va_list, va_start, va_arg et va_end, comme le montre l'exemple de l'article Wikipedia. C'est un peu plus lourd qu'en Java ou C #, car C et C++ ont seulement un support intégré limité pour varargs.

3
Jesper

Les réponses GManNickG et Christoph sont bonnes, mais les fonctions variadiques vous permettent d'introduire le paramètre ... tout ce que vous voulez, pas seulement des entiers. Si vous voulez voulez dans le futur, pousser de nombreuses variables et valeurs de types différents dans une fonction sans utiliser la fonction variadic, car c'est trop difficile ou trop compliqué pour vous, ou vous n'aimez pas la façon de l'utiliser ou vous ne voulez pas inclure les en-têtes requis pour l'utiliser, alors vous pouvez toujours utiliser void** paramètre.

Par exemple, Stephan202 a publié:

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

cela peut aussi s'écrire:

double average(int count, void** params)
{
    int j;
    double tot = 0;
    for (j=0; j<count; j++)
       tot+=*(double*)params[j];
    return tot/count;
}

Maintenant, utilisez-le comme ceci:

int _tmain(int argc, _TCHAR* argv[])
{
    void** params = new void*[3];
    double p1 = 1, p2 = 2, p3 = 3;
    params[0] = &p1;
    params[1] = &p2;
    params[2] = &p3;
    printf("Average is: %g\n", average(3, params));
    system("pause");
    return 0;
}

pour le code complet:

#include "stdafx"
#include <process.h>

double average(int count, void** params)
{
    int j;
    double tot = 0;
    for (j=0; j<count; j++)
        tot+=*(double*)params[j];
    return tot/count;
}

int _tmain(int argc, _TCHAR* argv[])
{
    void** params = new void*[3];
    double p1 = 1, p2 = 2, p3 = 3;
    params[0] = &p1;
    params[1] = &p2;
    params[2] = &p3;
    printf("Average is: %g\n", average(3, params));
    system("pause");
    return 0;
 }

SORTIE:

La moyenne est: 2

Appuyez sur n'importe quelle touche pour continuer . . .

1
user2133061

Je fais le mien comme ça dans c ++ builder xe.xx:

String s[] = {"hello ", " unli", " param", " test"};
String ret = BuildList(s, 4);

String BuildList(String s[], int count)
{
    for(int i = 0; i < count; i++)
    {
        //.... loop here up to last s[i] item ... 
    }
}
1
a_asiado

Si vous ne vous souciez pas de la portabilité, vous pouvez porter ce code C99 vers C++ en utilisant expressions de déclaration de gcc :

#include <cstdio>

int _sum(size_t count, int values[])
{
    int s = 0;
    while(count--) s += values[count];
    return s;
}

#define sum(...) ({ \
    int _sum_args[] = { __VA_ARGS__ }; \
    _sum(sizeof _sum_args / sizeof *_sum_args, _sum_args); \
})

int main(void)
{
    std::printf("%i", sum(1, 2, 3));
}

Vous pouvez faire quelque chose de similaire avec les expressions lambda C++ 0x, mais la version gcc que j'utilise (4.4.0) ne les prend pas en charge.

1
Christoph