web-dev-qa-db-fra.com

Alternative standard au truc ## __ VA_ARGS__ de GCC?

Il existe un connu _ { problème avec des arguments vides pour les macros variadiques dans C99.

exemple:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

L'utilisation de BAR() ci-dessus est en effet incorrecte selon la norme C99, car elle sera étendue à:

printf("this breaks!",);

Notez la virgule de fin - non réalisable.

Certains compilateurs (par exemple, Visual Studio 2010) se débarrasseront de cette virgule pour vous. D'autres compilateurs (par exemple, GCC) supportent de mettre ## devant __VA_ARGS__, comme ceci:

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

Mais existe-t-il un moyen conforme à la norme pour obtenir ce comportement? Peut-être en utilisant plusieurs macros?

Pour le moment, la version ## semble assez bien supportée (du moins sur mes plates-formes), mais je préférerais vraiment utiliser une solution conforme aux normes.

Préemptif: Je sais que je pourrais écrire une petite fonction. J'essaie de faire cela en utilisant des macros.

Edit: Voici un exemple (bien que simple) de la raison pour laquelle je voudrais utiliser BAR ():

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

Cela ajoute automatiquement une nouvelle ligne à mes instructions de journalisation BAR (), en supposant que fmt est toujours une chaîne de caractères C-guillemet. Il n'imprime PAS la nouvelle ligne en tant que printf () séparé, ce qui est avantageux si la consignation est stockée en mémoire tampon et provient de plusieurs sources de manière asynchrone.

131
jwd

Il est possible d'éviter l'utilisation de l'extension ,##__VA_ARGS__ de GCC si vous êtes prêt à accepter une limite supérieure codée en dur sur le nombre d'arguments que vous pouvez transmettre à votre macro variadique, comme décrit dans Réponse de Richard Hansen à cette question . Si vous ne souhaitez pas définir de telles limites, toutefois, à ma connaissance, il n'est pas possible d'utiliser uniquement les fonctions de pré-traitement spécifiées par C99. vous devez utiliser une extension de la langue. clang et icc ont adopté cette extension GCC, mais pas MSVC. 

En 2001, j’ai écrit l’extension GCC pour normalisation (et l’extension associée qui vous permet d’utiliser un nom autre que __VA_ARGS__ pour le paramètre reste) dans document N976 , mais qui n’a reçu aucune réponse du comité; Je ne sais même pas si quelqu'un l'a lu. En 2016, il a de nouveau été proposé dans N2023 , et j'encourage tous ceux qui savent comment cette proposition va nous le faire savoir dans les commentaires.

55
zwol

Il y a une astuce de comptage d'arguments que vous pouvez utiliser.

Voici un moyen conforme à la norme d'implémenter le deuxième exemple BAR() dans la question de jwd:

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

Ce même truc est utilisé pour:

Explication

La stratégie consiste à séparer __VA_ARGS__ dans le premier argument et le reste (le cas échéant). Cela permet d’insérer des éléments après le premier argument mais avant le second (si présent).

FIRST()

Cette macro s'étend simplement au premier argument, en éliminant le reste.

La mise en œuvre est simple. L'argument throwaway garantit que FIRST_HELPER() obtient deux arguments, ce qui est requis car le ... en nécessite au moins un. Avec un argument, il se développe comme suit:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

Avec deux ou plus, il se développe comme suit:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

Cette macro s'étend à tout sauf au premier argument (y compris la virgule après le premier argument, s'il y a plus d'un argument).

La mise en œuvre de cette macro est beaucoup plus compliquée. La stratégie générale consiste à compter le nombre d'arguments (un ou plusieurs), puis à développer REST_HELPER_ONE() (si un seul argument est fourni) ou REST_HELPER_TWOORMORE() (si deux arguments ou plus sont fournis). REST_HELPER_ONE() ne fait que s'étendre à rien - il n'y a pas d'argument après le premier, donc le dernier argument est l'ensemble vide. REST_HELPER_TWOORMORE() est également simple: il se développe en une virgule suivie de tout sauf du premier argument.

Les arguments sont comptés à l'aide de la macro NUM(). Cette macro se développe en ONE si un seul argument est donné, TWOORMORE si entre deux et neuf arguments sont donnés, et se casse si 10 arguments ou plus sont donnés (car il passe au dixième argument).

La macro NUM() utilise la macro SELECT_10TH() pour déterminer le nombre d'arguments. Comme son nom l'indique, SELECT_10TH() étend simplement son 10ème argument. En raison des Ellipsis, SELECT_10TH() doit recevoir au moins 11 arguments (la norme indique qu'il doit y avoir au moins un argument pour Ellipsis). C'est pourquoi NUM() transmet throwaway comme dernier argument (sans lui, passer un argument à NUM() aurait pour résultat que seuls 10 arguments seraient passés à SELECT_10TH(), ce qui violerait la norme).

La sélection de REST_HELPER_ONE() ou REST_HELPER_TWOORMORE() est effectuée en concaténant REST_HELPER_ avec le développement de NUM(__VA_ARGS__) dans REST_HELPER2(). Notez que l'objectif de REST_HELPER() est de s'assurer que NUM(__VA_ARGS__) est entièrement développé avant d'être concaténé avec REST_HELPER_.

L'expansion avec un argument va comme suit:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (vide)

L’expansion avec deux arguments ou plus va comme suit:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg
108
Richard Hansen

Ce n'est pas une solution générale, mais dans le cas de printf, vous pouvez ajouter une nouvelle ligne comme:

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

Je crois qu'il ignore les arguments supplémentaires qui ne sont pas référencés dans la chaîne de format. Donc, vous pourriez probablement même vous en tirer avec:

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

Je ne peux pas croire que C99 a été approuvé sans un moyen standard de le faire. AFAICT le problème existe aussi en C++ 11.

15
Marsh Ray

Il existe un moyen de gérer ce cas spécifique en utilisant quelque chose comme Boost.Preprocessor . Vous pouvez utiliser BOOST_PP_VARIADIC_SIZE pour vérifier la taille de la liste d’arguments, puis la développer de manière conditionnelle en une autre macro. L'inconvénient est qu'il ne peut pas distinguer les arguments 0 à 1, et la raison en devient évidente une fois que vous considérez ce qui suit:

BOOST_PP_VARIADIC_SIZE()      // expands to 1
BOOST_PP_VARIADIC_SIZE(,)     // expands to 2
BOOST_PP_VARIADIC_SIZE(,,)    // expands to 3
BOOST_PP_VARIADIC_SIZE(a)     // expands to 1
BOOST_PP_VARIADIC_SIZE(a,)    // expands to 2
BOOST_PP_VARIADIC_SIZE(,b)    // expands to 2
BOOST_PP_VARIADIC_SIZE(a,b)   // expands to 2
BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

La liste des macro-arguments vides consiste en un argument qui se trouve être vide.

Dans ce cas, nous avons de la chance car votre macro désirée a toujours au moins 1 argument, nous pouvons l'implémenter comme deux macros "surchargées":

#define BAR_0(fmt) printf(fmt "\n")
#define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)

Et puis une autre macro pour basculer entre eux, tels que:

#define BAR(...) \
    BOOST_PP_CAT(BAR_, BOOST_PP_GREATER(
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \
    /**/

ou

#define BAR(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        BAR_1, BAR_0)(__VA_ARGS__) \
    /**/

Celui que vous trouviez plus lisible (je préfère le premier car il vous donne un formulaire général pour surcharger les macros sur le nombre d’arguments).

Il est également possible de faire cela avec une seule macro en accédant à la liste d'arguments variables et en la modifiant, mais elle est beaucoup moins lisible et très spécifique à ce problème:

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_COMMA_IF( \
        BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \
    BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

Aussi, pourquoi n'y a-t-il pas BOOST_PP_ARRAY_ENUM_TRAILING? Cela rendrait cette solution beaucoup moins horrible.

Edit: bon, voici un BOOST_PP_ARRAY_ENUM_TRAILING, et une version qui l'utilise (c'est maintenant ma solution préférée):

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \
    BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \
    /**/

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/
10
DRayX

J'ai rencontré un problème similaire récemment et je pense qu'il existe une solution.

L'idée clé est qu'il existe un moyen d'écrire une macro NUM_ARGS pour compter le nombre d'arguments qu'une macro variadique est donnée. Vous pouvez utiliser une variante de NUM_ARGS pour générer NUM_ARGS_CEILING2, ce qui peut vous indiquer si une macro variadique est dotée d'un argument ou de plusieurs arguments. Ensuite, vous pouvez écrire votre macro Bar de sorte qu'elle utilise NUM_ARGS_CEILING2 et CONCAT pour envoyer ses arguments à l'une des deux macros d'assistance: une qui attend exactement un argument et l'autre qui attend un nombre variable d'arguments supérieur à 1. 

Voici un exemple où j'utilise cette astuce pour écrire la macro UNIMPLEMENTED, qui est très similaire à BAR:

ÉTAPE 1:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

ETAPE 1.5:

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

Étape 2:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

ÉTAPE 3:

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

Où CONCAT est mis en œuvre de la manière habituelle. En bref, si ce qui précède semble déroutant: l’objectif de CONCAT est de s’étendre à un autre "appel" de macro.

Notez que NUM_ARGS n'est pas utilisé. Je viens de l'inclure pour illustrer l'astuce de base ici. Voir Le blog P99 de Jens Gustedt pour un traitement agréable de celui-ci. 

Deux notes: 

  • NUM_ARGS est limité dans le nombre d'arguments qu'il gère. Mine Ne peut en gérer que 20, bien que le nombre soit totalement arbitraire. 

  • NUM_ARGS, comme indiqué, présente un piège en ce sens qu'il renvoie 1 lorsqu'il reçoit 0 argument. L’essentiel est que NUM_ARGS compte techniquement [virgules + 1], et non pas args. Dans ce cas particulier, cela fonctionne à notre avantage. _UNIMPLEMENTED1 gérera très bien un jeton vide Cela nous évitera d'avoir à écrire _UNIMPLEMENTED0. Gustedt a également une solution de contournement , Bien que je ne l’aie pas utilisée et que je ne sois pas sûr que cela fonctionne pour ce que nous faisons ici.

4
User123abc

Une macro très simple que j'utilise pour l'impression de débogage:

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "Nice!");
        return 0;
}

Quel que soit le nombre d'arguments transmis à DBG, il n'y a pas d'avertissement c99.

Le truc est __DBG_INT en ajoutant un paramètre factice afin que ... ait toujours au moins un argument et que c99 est satisfait.

2
SimonW

Ceci est la version simplifiée que j'utilise. Il est basé sur les grandes techniques des autres réponses présentées ici, et de nombreux accessoires:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

C'est tout.

Comme avec d'autres solutions, ceci est limité au nombre d'arguments de la macro. Pour en supporter davantage, ajoutez plus de paramètres à _SELECT et davantage d'arguments N. Les noms d’argument décomptent (au lieu de haut) pour rappeler que l’argument SUFFIX basé sur le décompte est fourni dans l’ordre inverse.

Cette solution traite 0 argument comme s'il s'agissait d'un argument. Donc, BAR() nominalement "fonctionne", car il se développe en _SELECT(_BAR,,N,N,N,N,1)(), qui se développe en _BAR_1()(), qui se développe en printf("\n").

Si vous le souhaitez, vous pouvez faire preuve de créativité avec l'utilisation de _SELECT et fournir différentes macros pour un nombre différent d'arguments. Par exemple, nous avons ici une macro LOG qui prend un argument 'level' avant le format. Si le format est manquant, il enregistre "(pas de message)", s'il n'y a qu'un argument, il sera enregistré via "% s", sinon, il traitera l'argument de format comme une chaîne de format printf pour les arguments restants.

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/
1
ɲeuroburɳ

C (gcc) , 762 octets

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

Essayez-le en ligne!

Suppose:

  • Aucun argument ne contient une virgule ou un support
  • Aucun argument ne contient A ~ G (peut renommer les noms hard_collide)
0
l4m2

Dans votre situation (au moins 1 argument présent, jamais 0), vous pouvez définir BAR en tant que BAR(...), utilisez Jens GustedtHAS_COMMA(...) pour détecter une virgule, puis expédiez les éléments BAR0(Fmt) ou BAR1(Fmt,...) en conséquence.

Ce:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

compile avec -pedantic sans avertissement.

0
PSkocik