web-dev-qa-db-fra.com

Paramètres facultatifs avec macros C ++

Existe-t-il un moyen d’obtenir des paramètres facultatifs avec les macros C++? Une sorte de surcharge serait bien aussi.

95
Cenoc

Voici une façon de le faire. Il utilise la liste des arguments deux fois, d'abord pour former le nom de la macro d'assistance, puis pour passer les arguments à cette macro d'assistance. Il utilise une astuce standard pour compter le nombre d'arguments d'une macro.

enum
{
    plain = 0,
    bold = 1,
    italic = 2
};

void PrintString(const char* message, int size, int style)
{
}

#define PRINT_STRING_1_ARGS(message)              PrintString(message, 0, 0)
#define PRINT_STRING_2_ARGS(message, size)        PrintString(message, size, 0)
#define PRINT_STRING_3_ARGS(message, size, style) PrintString(message, size, style)

#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define PRINT_STRING_MACRO_CHOOSER(...) \
    GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \
                PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )

#define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main(int argc, char * const argv[])
{
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 18);
    PRINT_STRING("Hello, World!", 18, bold);

    return 0;
}

Cela facilite la tâche de l'appelant de la macro, mais pas de l'auteur.

137
Derek Ledbetter

Avec tout le respect que je vous dois à Derek Ledbetter pour sa réponse - et nos excuses pour avoir relancé une vieille question.

Comprendre ce qu’il faisait et trouver ailleurs la capacité de précéder le __VA_ARGS__ avec ## m'a permis de proposer une variation ...

// The multiple macros that you would need anyway [as per: Crazy Eddie]
#define XXX_0()                     <code for no arguments> 
#define XXX_1(A)                    <code for one argument> 
#define XXX_2(A,B)                  <code for two arguments> 
#define XXX_3(A,B,C)                <code for three arguments> 
#define XXX_4(A,B,C,D)              <code for four arguments>  

// The interim macro that simply strips the excess and ends up with the required macro
#define XXX_X(x,A,B,C,D,FUNC, ...)  FUNC  

// The macro that the programmer uses 
#define XXX(...)                    XXX_X(,##__VA_ARGS__,\
                                          XXX_4(__VA_ARGS__),\
                                          XXX_3(__VA_ARGS__),\
                                          XXX_2(__VA_ARGS__),\
                                          XXX_1(__VA_ARGS__),\
                                          XXX_0(__VA_ARGS__)\
                                         ) 

Pour les non-experts comme moi qui tombent sur la réponse, mais ne peuvent pas voir comment cela fonctionne, je vais passer en revue le traitement, en commençant par le code suivant ...

XXX();
XXX(1); 
XXX(1,2); 
XXX(1,2,3); 
XXX(1,2,3,4); 
XXX(1,2,3,4,5);      // Not actually valid, but included to show the process 

Devient...

XXX_X(, XXX_4(), XXX_3(),  XXX_2(),    XXX_1(),      XXX_0()         );
XXX_X(, 1,       XXX_4(1), XXX_3(1),   XXX_2(1),     XXX_1(1),       XXX_0(1)          );
XXX_X(, 1,       2,        XXX_4(1,2), XXX_3(1,2),   XXX_2(1,2),     XXX_1(1,2),       XXX_0(1,2)        );
XXX_X(, 1,       2,        3,          XXX_4(1,2,3), XXX_3(1,2,3),   XXX_2(1,2,3),     XXX_1(1,2,3),     XXX_0(1,2,3)      );
XXX_X(, 1,       2,        3,          4,            XXX_4(1,2,3,4), XXX_3(1,2,3,4),   XXX_2(1,2,3,4),   XXX_1(1,2,3,4),   XXX_0(1,2,3,4)    );
XXX_X(, 1,       2,        3,          4,            5,              XXX_4(1,2,3,4,5), XXX_3(1,2,3,4,5), XXX_2(1,2,3,4,5), XXX_1(1,2,3,4,5), XXX_0(1,2,3,4,5) );

Ce qui devient juste le sixième argument ...

XXX_0(); 
XXX_1(1); 
XXX_2(1,2); 
XXX_3(1,2,3); 
XXX_4(1,2,3,4); 
5; 

PS: Supprimez #define pour XXX_0 pour obtenir une erreur de compilation [c'est-à-dire: si une option sans argument n'est pas autorisée].

PPS: Ce serait bien d’avoir les situations non valides (par exemple: 5) qui donnent une erreur de compilation plus claire au programmeur!

PPPS: Je ne suis pas un expert, je suis donc très heureux d'entendre des commentaires (bons, mauvais ou autres)!

74
David Sorkovsky

Les macros C++ n'ont pas changé depuis le C. Comme C n'avait pas d'argument de surcharge ni d'argument par défaut pour les fonctions, il ne les avait certainement pas pour les macros. Donc, pour répondre à votre question: non, ces fonctionnalités n'existent pas pour les macros. Votre seule option est de définir plusieurs macros avec des noms différents (ou de ne pas utiliser de macros du tout).

Sidenote: En C++, il est généralement considéré comme une bonne pratique de s’éloigner autant que possible des macros. Si vous avez besoin de telles fonctionnalités, vous risquez de trop utiliser des macros.

31
sepp2k

Avec le plus grand respect pour Derek Ledbetter, David Sorkovsky, Syphorlate pour leurs réponses, ainsi que la méthode ingénieuse de détection de vide arguments de macro par Jens Gustedt à

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

enfin, je viens avec quelque chose qui intègre toutes les astuces, de sorte que la solution

  1. Utilise uniquement les macros standard C99 pour obtenir une surcharge de fonctions, sans extension GCC/CLANG/MSVC (c'est-à-dire, avalée par une virgule à l'aide de l'expression spécifique , ##__VA_ARGS__ ] pour GCC/CLANG et ingestion implicite de ##__VA_ARGS__ pour MSVC). Alors n'hésitez pas à passer le --std=c99 Manquant à votre compilateur si vous le souhaitez =)
  2. Fonctionne pour zéro argument , ainsi que nombre illimité d'arguments , si vous l'élargissez pour répondre à vos besoins
  3. Fonctionne raisonnablement sur plusieurs plates-formes , du moins testé pour

    • GNU/Linux + GCC (GCC 4.9.2 sur CentOS 7.0 x86_64)
    • GNU/Linux + CLANG/LLVM , (CLANG/LLVM 3.5.0 sur CentOS 7.0 x86_64)
    • OS X + Xcode , (XCode 6.1.1 sur OS X Yosemite 10.10.1)
    • Windows + Visual Studio , (Visual Studio 2013 Update 4 sur Windows 7 SP1 64 bits)

Pour les lazies, il suffit de passer au tout dernier de ce message pour copier la source. Vous trouverez ci-dessous une explication détaillée qui, espérons-le, aidera et inspirera toutes les personnes à la recherche des solutions générales __VA_ARGS__ Comme moi. =)

Voici comment ça se passe. Définissez d’abord la "fonction" surchargée, visible par l’utilisateur, que j’ai nommée create, ainsi que la définition de la fonction correspondante realCreate et les définitions de macros avec un nombre différent d’arguments CREATE_2, CREATE_1, CREATE_0, Comme indiqué ci-dessous:

#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

La partie MACRO_CHOOSER(__VA_ARGS__) résout finalement les noms de définition de macro, et la deuxième partie (__VA_ARGS__) Comprend leurs listes de paramètres. L'appel d'un utilisateur à create(10) résout alors CREATE_1(10), la partie CREATE_1 Provient de MACRO_CHOOSER(__VA_ARGS__) et la partie (10) à partir du deuxième (__VA_ARGS__).

MACRO_CHOOSER Utilise le truc selon lequel, si __VA_ARGS__ Est vide, l'expression suivante est concaténée en un appel de macro valide par le pré-processeur:

NO_ARG_EXPANDER __VA_ARGS__ ()  // simply shrinks to NO_ARG_EXPANDER()

Ingeniusly, nous pouvons définir cet appel de macro résultant comme

#define NO_ARG_EXPANDER() ,,CREATE_0

Notez les deux virgules, elles sont expliquées bientôt. La prochaine macro utile est

#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())

donc les appels de

create();
create(10);
create(20, 20);

sont réellement étendus à

CHOOSE_FROM_ARG_COUNT(,,CREATE_0)();
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 10 ())(10);
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 20, 20 ())(20, 20);

Comme le suggère le nom de la macro, nous devons compter le nombre d'arguments ultérieurement. Voici une autre astuce: le préprocesseur effectue uniquement le remplacement de texte simple. Il déduit le nombre d'arguments d'un appel de macro uniquement du nombre de virgules qu'il voit à l'intérieur des parenthèses. Les "arguments" réels séparés par des virgules n'ont pas besoin d'être d'une syntaxe valide. Ils peuvent être n'importe quel texte. Cela signifie que, dans l'exemple ci-dessus, NO_ARG_EXPANDER 10 () est compté comme un argument pour l'appel du milieu. NO_ARG_EXPANDER 20 Et 20 () sont comptés comme 2 arguments pour l'appel du bas, respectivement.

Si nous utilisons les macros auxiliaires suivantes pour les développer davantage

##define CHOOSE_FROM_ARG_COUNT(...) \
  FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define FUNC_RECOMPOSER(argsWithParentheses) \
  FUNC_CHOOSER argsWithParentheses

La fin de , Après CREATE_1 Est une solution de contournement pour GCC/CLANG, supprimant une erreur (faux positif) disant que ISO C99 requires rest arguments to be used Lors de la transmission de -pedantic À votre compilateur. Le FUNC_RECOMPOSER Est une solution de contournement pour MSVC, ou il ne peut pas compter le nombre d'arguments (c'est-à-dire des virgules) à l'intérieur des parenthèses des appels de macros correctement. Les résultats sont en outre résolus à

FUNC_CHOOSER (,,CREATE_0, CREATE_2, CREATE_1, )();
FUNC_CHOOSER (NO_ARG_EXPANDER 10 (), CREATE_2, CREATE_1, )(10);
FUNC_CHOOSER (NO_ARG_EXPANDER 20, 20 (), CREATE_2, CREATE_1, )(20, 20);

Comme vous l'avez peut-être vu, la dernière étape dont nous avons besoin est d'utiliser une astuce de comptage d'arguments standard pour choisir les noms de version de macro recherchés:

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3

qui résout les résultats à

CREATE_0();
CREATE_1(10);
CREATE_2(20, 20);

et nous donne certainement les appels de fonction réels souhaités:

realCreate(0, 0);
realCreate(10, 10);
realCreate(20, 20);

En résumé, avec quelques réarrangements d'instructions pour une meilleure lisibilité, la source entière de l'exemple à 2 arguments est ici:

#include <stdio.h>

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define NO_ARG_EXPANDER() ,,CREATE_0
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())
#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main()
{
  create();
  create(10);
  create(20, 20);
  //create(30, 30, 30);  // Compilation error
  return 0;
}

Bien que compliqué, moche et accablant pour le développeur d’API, il existe une solution pour surcharger et définir les paramètres facultatifs des fonctions C/C++ pour nous, les fous. L'utilisation des API surchargées qui en sortent devient très agréable et agréable. =)

Si cette approche peut encore être simplifiée, veuillez me le faire savoir à l'adresse suivante:

https://github.com/jason-deng/C99FunctionOverload

Encore une fois un merci spécial à toutes les personnes brillantes qui m'ont inspiré et amené à réaliser ce travail! =)

22
Jason Deng

Pour tous ceux qui cherchent péniblement une solution VA_NARGS qui fonctionne avec Visual C++. La macro suivante a parfaitement fonctionné pour moi (même avec zéro paramètre!) Dans Visual C++ Express 2010:

#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,N,...) N
#define VA_NUM_ARGS_IMPL_(Tuple) VA_NUM_ARGS_IMPL Tuple
#define VA_NARGS(...)  bool(#__VA_ARGS__) ? (VA_NUM_ARGS_IMPL_((__VA_ARGS__, 24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))) : 0

Si vous voulez une macro avec des paramètres optionnels, vous pouvez faire:

//macro selection(vc++)
#define SELMACRO_IMPL(_1,_2,_3, N,...) N
#define SELMACRO_IMPL_(Tuple) SELMACRO_IMPL Tuple
#define mymacro1(var1) var1
#define mymacro2(var1,var2) var2*var1
#define mymacro3(var1,var2,var3) var1*var2*var3
#define mymacro(...) SELMACRO_IMPL_((__VA_ARGS__, mymacro3(__VA_ARGS__), mymacro2(__VA_ARGS__), mymacro1(__VA_ARGS__))) 

Cela a fonctionné pour moi aussi dans vc. Mais cela ne fonctionne pas pour zéro paramètre.

int x=99;
x=mymacro(2);//2
x=mymacro(2,2);//4
x=mymacro(2,2,2);//8
9
Syphorlate

gcc/g++ supporte macros varargs mais je ne pense pas que cela soit standard, utilisez-le à vos risques et périls.

6
Paul R
#include <stdio.h>

#define PP_NARG(...) \
    PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
    PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
    _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ 
    _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
    _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
    _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
    _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
    _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
    _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
    63,62,61,60,                   \
    59,58,57,56,55,54,53,52,51,50, \
    49,48,47,46,45,44,43,42,41,40, \
    39,38,37,36,35,34,33,32,31,30, \
    29,28,27,26,25,24,23,22,21,20, \
    19,18,17,16,15,14,13,12,11,10, \
    9,8,7,6,5,4,3,2,1,0

#define PP_CONCAT(a,b) PP_CONCAT_(a,b)
#define PP_CONCAT_(a,b) a ## b

#define THINK(...) PP_CONCAT(THINK_, PP_NARG(__VA_ARGS__))(__VA_ARGS__)
#define THINK_0() THINK_1("sector zz9 plural z alpha")
#define THINK_1(location) THINK_2(location, 42)
#define THINK_2(location,answer) THINK_3(location, answer, "deep thought")
#define THINK_3(location,answer,computer) \
  printf ("The answer is %d. This was calculated by %s, and a computer to figure out what this"
          " actually means will be build in %s\n", (answer), (computer), (location))

int
main (int argc, char *argv[])
{
  THINK (); /* On compilers other than GCC you have to call with least one non-default argument */
}

AVERTISSEMENT: la plupart du temps inoffensif.

5
Joe D

Ce n'est pas vraiment ce pour quoi le préprocesseur est conçu.

Cela dit, si vous souhaitez entrer dans le domaine de la programmation macro avec un minimum de lisibilité, vous devriez jeter un oeil à la bibliothèque de pré-processeur Boost . Après tout, il ne s'agirait pas de C++ s'il n'y avait pas trois niveaux de programmation totalement compatibles avec Turing (préprocesseur, métaprogrammation de modèles et C++ de base)!

3
Pontus Gagge
#define MY_MACRO_3(X,Y,Z) ...
#define MY_MACRO_2(X,Y) MY_MACRO(X,Y,5)
#define MY_MACRO_1(X) MY_MACRO(X,42,5)

Vous savez au moment de l'appel combien d'arguments vous allez transmettre, il n'y a donc vraiment pas besoin de surcharge.

3
Edward Strange

Version plus concise du code de Derek Ledbetter:

enum
{
    plain = 0,
    bold = 1,
    italic = 2
};


void PrintString(const char* message = NULL, int size = 0, int style = 0)
{
}


#define PRINT_STRING(...) PrintString(__VA_ARGS__)


int main(int argc, char * const argv[])
{ 
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 18);
    PRINT_STRING("Hello, World!", 18, bold);

    return 0;
}
2
Megamozg

Vous pouvez utiliser BOOST_PP_OVERLOAD d'une bibliothèque boost.

Exemple de officiel boost doc :

#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/arithmetic/add.hpp>

#define MACRO_1(number) MACRO_2(number,10)
#define MACRO_2(number1,number2) BOOST_PP_ADD(number1,number2)

#if !BOOST_PP_VARIADICS_MSVC

#define MACRO_ADD_NUMBERS(...) BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__)

#else

// or for Visual C++

#define MACRO_ADD_NUMBERS(...) \
  BOOST_PP_CAT(BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())

#endif

MACRO_ADD_NUMBERS(5) // output is 15
MACRO_ADD_NUMBERS(3,6) // output is 9
1
disable13

En tant que grand fan d'horribles monstres macro, je voulais développer la réponse de Jason Deng et la rendre réellement utilisable. (Pour le meilleur ou pour le pire.) L'original n'est pas très agréable à utiliser car vous devez modifier la grosse soupe de l'alphabet chaque fois que vous souhaitez créer une nouvelle macro. C'est encore pire si vous avez besoin d'une quantité d'arguments différente.

J'ai donc créé une version avec ces fonctionnalités:

  • 0 argumentation fonctionne
  • 1 à 16 arguments sans aucune modification de la partie désordonnée
  • Facile d'écrire plus de fonctions macro
  • Testé dans gcc 10, clang 9, Visual Studio 2017

Actuellement, je viens de faire 16 arguments maximum, mais si vous avez besoin de plus (vraiment maintenant? Vous devenez idiot ...), vous pouvez éditer FUNC_CHOOSER et CHOOSE_FROM_ARG_COUNT, puis ajouter des virgules à NO_ARG_EXPANDER.

Veuillez consulter l'excellente réponse de Jason Deng pour plus de détails sur la mise en œuvre, mais je vais simplement mettre le code ici:

#include <stdio.h>

void realCreate(int x, int y)
{
    printf("(%d, %d)\n", x, y);
}

// This part you put in some library header:
#define FUNC_CHOOSER(_f0, _f1, _f2, _f3, _f4, _f5, _f6, _f7, _f8, _f9, _f10, _f11, _f12, _f13, _f14, _f15, _f16, ...) _f16
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(F, ...) FUNC_RECOMPOSER((__VA_ARGS__, \
            F##_16, F##_15, F##_14, F##_13, F##_12, F##_11, F##_10, F##_9, F##_8,\
            F##_7, F##_6, F##_5, F##_4, F##_3, F##_2, F##_1, ))
#define NO_ARG_EXPANDER(FUNC) ,,,,,,,,,,,,,,,,FUNC ## _0
#define MACRO_CHOOSER(FUNC, ...) CHOOSE_FROM_ARG_COUNT(FUNC, NO_ARG_EXPANDER __VA_ARGS__ (FUNC))
#define MULTI_MACRO(FUNC, ...) MACRO_CHOOSER(FUNC, __VA_ARGS__)(__VA_ARGS__)

// When you need to make a macro with default arguments, use this:
#define create(...) MULTI_MACRO(CREATE, __VA_ARGS__)
#define CREATE_0() CREATE_1(0)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_2(x, y) \
    do { \
        /* put whatever code you want in the last macro */ \
        realCreate(x, y); \
    } while(0)


int main()
{
    create();
    create(10);
    create(20, 20);
    //create(30, 30, 30);  // Compilation error
    return 0;
}
0
Kuukunen

Selon ce dont vous avez besoin, vous pouvez le faire avec var args avec des macros. Maintenant, paramètres optionnels ou surcharge de macro, cela n'existe pas.

0
Gianni