Comment puis-je écrire une fonction qui accepte un nombre variable d'arguments? Est-ce possible, comment?
Comment puis-je écrire une fonction qui accepte un nombre variable d'arguments? Est-ce possible, comment?
Réponses:
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_list
type 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.
...
syntaxe?
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.
<cstdarg>
en C ++ au lieu de<stdarg.h>
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 gcc
ou clang
nous 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. [...]
typename
vs est class
-elle intentionnelle ci-dessus? Si oui, veuillez expliquer.
initializer_list
récursive?
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 :
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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
et 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 " Head
est la même chose que tout Tail...
".
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!
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.
,
opérateur avec des expressions de pli). Sinon, je ne pense pas.
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' printf
fonction prend des arguments variables, l' c++ cout
objet 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).
std::initializer_lists
... Et cela introduit déjà une complexité énorme sur une tâche simple.
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 ++ ...
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.
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 myfunc
avec 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 i
et de donner uniquement j
.
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.
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...
Utilisation de modèles variadiques, exemple pour reproduire console.log
comme 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;
}
};
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.
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);
}
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.
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