Alternative standard à l'astuce ## __ VA_ARGS__ de GCC?


151

Il existe un problème bien connu avec les 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 s'étendra à:

printf("this breaks!",);

Notez la virgule de fin - pas réalisable.

Certains compilateurs (par exemple: Visual Studio 2010) se débarrasseront discrètement de cette virgule de fin pour vous. D'autres compilateurs (par exemple: GCC) prennent en charge la mise ##devant __VA_ARGS__, comme ceci:

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

Mais existe-t-il un moyen conforme aux normes d'obtenir ce comportement? Peut-être utiliser plusieurs macros?

À l'heure actuelle, la ##version semble assez bien prise en charge (du moins sur mes plateformes), mais je préfère vraiment utiliser une solution conforme aux normes.

Préemptif: je sais que je pourrais simplement écrire une petite fonction. J'essaye de faire ceci 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 qu'il fmts'agit toujours d'une chaîne C entre guillemets. Il n'imprime PAS la nouvelle ligne en tant que printf () distincte, ce qui est avantageux si la journalisation est mise en tampon en ligne et provient de plusieurs sources de manière asynchrone.


3
Pourquoi utiliser BARau lieu de FOOen premier lieu?
GManNickG

@GMan: J'ai ajouté un exemple à la fin
jwd

5
@GMan: Lire la dernière phrase (:
jwd

7
Cette fonctionnalité a été proposée pour inclusion dans C2x.
Leushenko

2
@zwol la dernière version soumise au WG14 ressemble à ceci , qui utilise une nouvelle syntaxe basée sur le __VA_OPT__mot - clé. Cela a déjà été "adopté" par C ++, donc je pense que C suivra. (je ne sais pas si cela signifie qu'il a été accéléré dans C ++ 17 ou s'il est défini pour C ++ 20)
Leushenko

Réponses:


66

Il est possible d'éviter l'utilisation de l' ,##__VA_ARGS__extension de GCC si vous êtes prêt à accepter une limite supérieure codée en dur sur le nombre d'arguments que vous pouvez passer à votre macro variadique, comme décrit dans la réponse de Richard Hansen à cette question . Si vous ne souhaitez pas avoir une telle limite, cependant, à ma connaissance, il n'est pas possible d'utiliser uniquement les fonctionnalités de préprocesseur 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 la normalisation (et l'extension connexe qui vous permet d'utiliser un nom autre que __VA_ARGS__pour le paramètre rest) dans le 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 été proposé à nouveau dans N2023 , et j'encourage tous ceux qui savent comment cette proposition va nous le faire savoir dans les commentaires.


2
A en juger par mon handicap pour trouver une solution sur le web et le manque de réponses ici, je suppose que vous avez raison):
jwd

2
N976 est- il ce à quoi vous faites référence? Je cherchai le reste du groupe de travail C de documents pour une réponse , mais jamais trouvé un. Ce n'était même pas à l' ordre du jour de la réunion suivante . Le seul autre succès sur ce sujet était le commentaire n ° 4 de la Norvège au n868 avant la ratification de la C99 (encore une fois sans discussion de suivi).
Richard Hansen

4
Oui, en particulier la seconde moitié de cela. Il y a peut-être eu une discussion sur comp.std.cmais je n'ai pas pu en trouver dans Google Groupes pour le moment; il n'a certainement jamais attiré l'attention du comité lui-même (ou si c'était le cas, personne ne m'en a jamais parlé).
zwol

1
J'ai peur de ne pas avoir de preuve, et je ne suis plus la bonne personne pour essayer d'en imaginer une. J'ai écrit la moitié du préprocesseur de GCC, mais c'était il y a plus de dix ans, et je n'aurais jamais pensé à l'astuce de comptage d'arguments ci-dessous, même alors.
zwol

6
Cette extension fonctionne avec les compilateurs icc clang & intel, ainsi qu'avec gcc.
ACyclique du

112

Il existe une astuce pour compter les arguments que vous pouvez utiliser.

Voici un moyen conforme aux normes d'implémenter le deuxième BAR()exemple 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;
}

Cette même astuce est utilisée pour:

Explication

La stratégie consiste à séparer __VA_ARGS__le premier argument et le reste (le cas échéant). Cela permet d'insérer des trucs après le premier argument mais avant le second (s'il est présent).

FIRST()

Cette macro se développe simplement jusqu'au premier argument, rejetant le reste.

La mise en œuvre est simple. L' throwawayargument garantit qu'il FIRST_HELPER()obtient deux arguments, ce qui est obligatoire car il en ...faut 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 bien plus compliquée. La stratégie générale consiste à compter le nombre d'arguments (un ou plus d'un), puis à étendre jusqu'à REST_HELPER_ONE()(si un seul argument est donné) ou REST_HELPER_TWOORMORE()(si deux arguments ou plus sont donnés). REST_HELPER_ONE()se développe simplement en rien - il n'y a pas d'arguments après le premier, donc les arguments restants sont 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 NUM()macro. Cette macro se développe ONEsi un seul argument est donné, TWOORMOREsi entre deux et neuf arguments sont donnés, et s'interrompt si 10 arguments ou plus sont donnés (car elle se développe jusqu'au 10ème argument).

La NUM()macro utilise la SELECT_10TH()macro pour déterminer le nombre d'arguments. Comme son nom l'indique, SELECT_10TH()se développe simplement jusqu'à son 10ème argument. En raison des points de suspension, SELECT_10TH()doit être passé au moins 11 arguments (la norme dit qu'il doit y avoir au moins un argument pour les points de suspension). C'est pourquoi NUM()passe throwawaycomme dernier argument (sans lui, passer un argument à NUM()aurait pour effet de ne transmettre que 10 arguments SELECT_10TH(), ce qui violerait la norme).

La sélection de l'un REST_HELPER_ONE()ou de l'autre REST_HELPER_TWOORMORE()se fait par concaténation REST_HELPER_avec l'expansion de NUM(__VA_ARGS__)in REST_HELPER2(). Notez que le but de REST_HELPER()est de s'assurer qu'il NUM(__VA_ARGS__)est entièrement développé avant d'être concaténé avec REST_HELPER_.

L'expansion avec un argument se déroule 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 se déroule 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

1
Notez que cela échouera si vous appelez BAR avec 10 arguments ou plus, et bien qu'il soit relativement facile de l'étendre à plus d'arguments, il aura toujours une limite supérieure sur le nombre d'arguments qu'il peut traiter
Chris Dodd

2
@ChrisDodd: Correct. Malheureusement, il ne semble pas y avoir de moyen d'éviter une limite dans le nombre d'arguments sans s'appuyer sur des extensions spécifiques au compilateur. De plus, je ne connais pas un moyen de tester de manière fiable s'il y a trop d'arguments (afin qu'un message d'erreur utile du compilateur puisse être affiché, plutôt qu'un échec étrange).
Richard Hansen

17

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 tous les arguments supplémentaires qui ne sont pas référencés dans la chaîne de format. Vous pourriez donc probablement même vous en sortir 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 une manière standard de le faire. AFAICT le problème existe également en C ++ 11.


le problème avec ce 0 supplémentaire est qu'il finira réellement dans le code s'il appelle la fonction vararg. Vérifiez la solution fournie par Richard Hansen
Pavel P

@Pavel a raison sur le deuxième exemple, mais le premier fonctionne très bien. +1.
kirbyfan64sos

11

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 étendre conditionnellement à une autre macro. Le seul inconvénient de ceci est qu'il ne peut pas faire la distinction entre 0 et 1 argument, et la raison en devient claire 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 d'arguments de macro vide se compose en fait d'un argument qui se trouve être vide.

Dans ce cas, nous avons de la chance puisque votre macro souhaitée a toujours au moins 1 argument, nous pouvons l'implémenter comme deux macros de "surcharge":

#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, comme:

#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 trouvez le plus lisible (je préfère le premier car il vous donne une forme générale pour surcharger les macros sur le nombre d'arguments).

Il est également possible de le faire avec une seule macro en accédant et en faisant muter la liste des arguments de variable, mais c'est beaucoup moins lisible, et est 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 de BOOST_PP_ARRAY_ENUM_TRAILING? Cela rendrait cette solution beaucoup moins horrible.

Edit: Très bien, 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__)))) \
    /**/

1
Ravi d'en savoir plus sur Boost.Preprocessor, +1. Notez qu'il BOOST_PP_VARIADIC_SIZE()utilise la même astuce de comptage d'arguments que j'ai documentée dans ma réponse, et a la même limitation (elle se cassera si vous passez plus d'un certain nombre d'arguments).
Richard Hansen

1
Oui, j'ai vu que votre approche était la même que celle utilisée par Boost, mais la solution Boost est très bien entretenue et possède de nombreuses autres fonctionnalités vraiment utiles à utiliser lors du développement de macros plus sophistiquées. Le truc de récursivité est particulièrement cool (et utilisé dans les coulisses de la dernière approche qui utilise BOOST_PP_ARRAY_ENUM).
DRayX

1
Une réponse Boost qui s'applique réellement à la balise c ! Hourra!
Justin

6

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.

L'astuce consiste à __DBG_INTajouter un paramètre factice donc ...aura toujours au moins un argument et c99 est satisfait.


5

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

L'idée clé est qu'il existe un moyen d'écrire une macro NUM_ARGSpour compter le nombre d'arguments qu'une macro variadique est donnée. Vous pouvez utiliser une variante de NUM_ARGSto build NUM_ARGS_CEILING2, qui peut vous dire si une macro variadique reçoit 1 argument ou 2 arguments ou plus. Ensuite, vous pouvez écrire votre Barmacro pour qu'elle utilise NUM_ARGS_CEILING2et CONCATenvoyer ses arguments à l'une des deux macros d'assistance: une qui attend exactement 1 argument, et une 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

ÉTAPE 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 implémenté de la manière habituelle. En guise d'indication rapide, si ce qui précède semble déroutant: le but de CONCAT est de s'étendre à une autre macro "appel".

Notez que NUM_ARGS lui-même n'est pas utilisé. Je l'ai juste inclus pour illustrer l'astuce de base ici. Voir le blog P99 de Jens Gustedt pour un bon traitement.

Deux notes:

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

  • NUM_ARGS, comme indiqué, présente un inconvénient en ce sens qu'il renvoie 1 lorsqu'il est donné 0 argument. L'essentiel est que NUM_ARGS compte techniquement [virgules + 1], et non args. Dans ce cas particulier, cela fonctionne à notre avantage. _UNIMPLEMENTED1 gérera très bien un jeton vide et cela nous évitera d'avoir à écrire _UNIMPLEMENTED0. Gustedt a également une solution de contournement pour cela, même si je ne l'ai pas utilisé et je ne suis pas sûr que cela fonctionnerait pour ce que nous faisons ici.


+1 pour avoir
Richard Hansen

Les commentaires que vous avez ajoutés étaient une amélioration, mais il y a encore un certain nombre de problèmes: 1. Vous discutez et définissez NUM_ARGSmais ne l'utilisez pas. 2. Quel est le but de UNIMPLEMENTED? 3. Vous ne résolvez jamais le problème d'exemple dans la question. 4. Parcourir l'expansion une étape à la fois illustrerait son fonctionnement et expliquerait le rôle de chaque macro d'aide. 5. Discuter de 0 argument est distrayant; l'OP posait des questions sur la conformité aux normes, et 0 argument est interdit (C99 6.10.3p4). 6. Étape 1.5? Pourquoi pas l'étape 2? 7. Les "étapes" impliquent des actions qui se produisent séquentiellement; ce n'est que du code.
Richard Hansen

8. Vous créez un lien vers l'ensemble du blog, pas vers le message concerné. Je n'ai pas trouvé le message auquel vous faisiez référence. 9. Le dernier paragraphe est maladroit: cette méthode est obscure; c'est pourquoi personne d'autre n'avait publié de solution correcte auparavant. De plus, si cela fonctionne et adhère à la norme, la réponse de Zack doit être fausse. 10. Vous devez définir CONCAT()- ne supposez pas que les lecteurs savent comment cela fonctionne.
Richard Hansen

(Veuillez ne pas interpréter ce commentaire comme une attaque - je voulais vraiment voter pour votre réponse, mais je ne me sentais pas à l'aise de le faire à moins que cela ne soit plus facile à comprendre. Si vous pouvez améliorer la clarté de votre réponse, je voter pour le vôtre et supprimer le mien.)
Richard Hansen

2
Je n'aurais jamais pensé à cette approche, et j'ai écrit à peu près la moitié du préprocesseur actuel de GCC! Cela dit, je dis toujours qu '"il n'y a pas de méthode standard pour obtenir cet effet" parce que vos techniques et celles de Richard imposent une limite supérieure au nombre d'arguments de la macro.
zwol

2

C'est la version simplifiée que j'utilise. Il est basé sur les grandes techniques des autres réponses ici, tant d'accessoires pour eux:

#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 pour les autres solutions, cela est limité au nombre d'arguments de la macro. Pour en prendre en charge plus, ajoutez plus de paramètres _SELECTet plus d' Narguments. Les noms d'argument comptent à rebours (au lieu de monter) pour rappeler que l' SUFFIXargument basé sur le nombre 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 s'étend vers _SELECT(_BAR,,N,N,N,N,1)(), qui s'étend vers _BAR_1()(), qui s'étend vers printf("\n").

Si vous le souhaitez, vous pouvez faire preuve de créativité en utilisant _SELECTet fournir différentes macros pour différents nombres d'arguments. Par exemple, nous avons ici une macro LOG qui prend un argument «niveau» avant le format. Si le format est manquant, il enregistre "(pas de message)", s'il n'y a qu'un seul argument, il le consignera via "% s", sinon il traitera l'argument 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
*/

Cela déclenche toujours un avertissement lors de la compilation avec -pedantic.
PSkocik

1

Dans votre situation (au moins 1 argument présent, jamais 0), vous pouvez définir BARcomme BAR(...), utiliser Jens Gustedt's HAS_COMMA(...) pour détecter une virgule, puis envoyer vers BAR0(Fmt)ou en BAR1(Fmt,...)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 -pedanticsans avertissement.


0

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 de virgule ou de crochet
  • Aucun argument ne contient A~ G(peut être renommé en hard_collide)

La no arg contain commalimitation peut être contournée en vérifiant multi après quelques passages supplémentaires, mais no brackettoujours là
l4m2

-2

La solution standard consiste à utiliser à la FOOplace de BAR. Il y a quelques cas étranges de réorganisation d'arguments que cela ne peut probablement pas faire pour vous (bien que je parie que quelqu'un peut trouver des hacks intelligents à démonter et à remonter __VA_ARGS__conditionnellement en fonction du nombre d'arguments!) Mais en général en utilisant FOO"habituellement" fonctionne juste.


1
La question était "existe-t-il un moyen conforme aux normes d'obtenir ce comportement?"
Marsh Ray

2
Et la question a inclus une justification pour ne pas utiliser FOO depuis des lustres maintenant.
Pavel Šimerda
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.