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 fmt
s'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.
BAR
au lieu deFOO
en premier lieu?__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)Réponses:
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.la source
comp.std.c
mais 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é).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:Cette même astuce est utilisée pour:
__VA_ARGS__
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'
throwaway
argument garantit qu'ilFIRST_HELPER()
obtient deux arguments, ce qui est obligatoire car il en...
faut au moins un. Avec un argument, il se développe comme suit:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Avec deux ou plus, il se développe comme suit:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
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é) ouREST_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éveloppeONE
si un seul argument est donné,TWOORMORE
si 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 laSELECT_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 pourquoiNUM()
passethrowaway
comme dernier argument (sans lui, passer un argument àNUM()
aurait pour effet de ne transmettre que 10 argumentsSELECT_10TH()
, ce qui violerait la norme).La sélection de l'un
REST_HELPER_ONE()
ou de l'autreREST_HELPER_TWOORMORE()
se fait par concaténationREST_HELPER_
avec l'expansion deNUM(__VA_ARGS__)
inREST_HELPER2()
. Notez que le but deREST_HELPER()
est de s'assurer qu'ilNUM(__VA_ARGS__)
est entièrement développé avant d'être concaténé avecREST_HELPER_
.L'expansion avec un argument se déroule comme suit:
REST(firstarg)
REST_HELPER(NUM(firstarg), firstarg)
REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
REST_HELPER2(ONE, firstarg)
REST_HELPER_ONE(firstarg)
L'expansion avec deux arguments ou plus se déroule comme suit:
REST(firstarg, secondarg, thirdarg)
REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
, secondarg, thirdarg
la source
Pas une solution générale, mais dans le cas de printf, vous pouvez ajouter une nouvelle ligne comme:
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:
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.
la source
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:
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":
Et puis une autre macro pour basculer entre eux, comme:
ou
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:
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):
la source
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).Une macro très simple que j'utilise pour l'impression de débogage:
Quel que soit le nombre d'arguments transmis à DBG, il n'y a pas d'avertissement c99.
L'astuce consiste à
__DBG_INT
ajouter un paramètre factice donc...
aura toujours au moins un argument et c99 est satisfait.la source
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_ARGS
pour compter le nombre d'arguments qu'une macro variadique est donnée. Vous pouvez utiliser une variante deNUM_ARGS
to buildNUM_ARGS_CEILING2
, qui peut vous dire si une macro variadique reçoit 1 argument ou 2 arguments ou plus. Ensuite, vous pouvez écrire votreBar
macro pour qu'elle utiliseNUM_ARGS_CEILING2
etCONCAT
envoyer 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:
ÉTAPE 1.5:
Étape 2:
ÉTAPE 3:
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.
la source
NUM_ARGS
mais ne l'utilisez pas. 2. Quel est le but deUNIMPLEMENTED
? 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.CONCAT()
- ne supposez pas que les lecteurs savent comment cela fonctionne.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:
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
_SELECT
et plus d'N
arguments. Les noms d'argument comptent à rebours (au lieu de monter) pour rappeler que l'SUFFIX
argument 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 versprintf("\n")
.Si vous le souhaitez, vous pouvez faire preuve de créativité en utilisant
_SELECT
et 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.la source
Dans votre situation (au moins 1 argument présent, jamais 0), vous pouvez définir
BAR
commeBAR(...)
, utiliser Jens Gustedt'sHAS_COMMA(...)
pour détecter une virgule, puis envoyer versBAR0(Fmt)
ou enBAR1(Fmt,...)
conséquence.Ce:
compile avec
-pedantic
sans avertissement.la source
C (gcc) , 762 octets
Essayez-le en ligne!
Suppose:
A
~G
(peut être renommé en hard_collide)la source
no arg contain comma
limitation peut être contournée en vérifiant multi après quelques passages supplémentaires, maisno bracket
toujours làLa solution standard consiste à utiliser à la
FOO
place deBAR
. 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 utilisantFOO
"habituellement" fonctionne juste.la source