Standard alternative de GCC ##__VA_ARGS__ tromper?
Il y a un bien connu problème avec vide d'argument pour variadic macros en 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 incorrect selon le standard C99, car elle sera étendue à d':
printf("this breaks!",);
Notez la virgule - pas réalisable.
Certains compilateurs (ex: Visual Studio 2010) va tranquillement se débarrasser de la virgule pour vous. D'autres compilateurs (par exemple: GCC) favoriser la mise ##
en face de __VA_ARGS__
, comme suit:
#define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__)
Mais est-il conforme aux normes façon d'obtenir ce comportement?
Peut-être à l'aide de plusieurs macros?
Droit maintenant, le ##
version semble assez bien pris en charge (au moins sur mes plates-formes), mais j'aimerais vraiment utiliser plutôt conforme aux normes de la solution.
De préemption: je sais que je pourrais juste écrire une petite fonction. Je suis en train de le faire à l'aide de macros.
Modifier: Voici un exemple (mais simple) de pourquoi je veux utiliser la BARRE():
#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 un saut de ligne à mon BAR() enregistrement des déclarations, en supposant fmt
est toujours un double de la cité, le C-string. Il n'a PAS l'impression que le saut de ligne séparés comme printf(), ce qui est avantageux si l'enregistrement est en ligne de tampon et provenant de sources multiples de manière asynchrone.
- Pourquoi utiliser
BAR
au lieu deFOO
en premier lieu? - J'ai ajouté un exemple à la fin
- Dans ce cas, vous pouvez simplement faire un multi-déclaration de la macro avec un
printf("\n");
à la fin. - Lire la dernière phrase (:
- Cette fonction a été proposé d'inscrire dans C2x.
- Auriez-vous par hasard à connaître l'état de cette proposition dès maintenant (un peu plus d'un an plus tard)?
- la dernière version soumise à WG14 ressemble à ceci, qui utilise une nouvelle syntaxe
__VA_OPT__
mot-clé. Cette a déjà été "adopté" en C++, donc je m'attends C suivront. (ne sais pas si cela signifie qu'il a été accélérée en C++17 ou si elle est définie pour le C++20 si)
Vous devez vous connecter pour publier un commentaire.
Il est possible d'éviter l'utilisation de GCC est
,##__VA_ARGS__
extension si vous êtes prêt à accepter une certaine codé en dur limite supérieure sur le nombre d'arguments, vous pouvez passer votre variadic macro, comme décrit dans Richard Hansen, la réponse à cette question. Si vous ne voulez pas avoir une telle limite, toutefois, à ma connaissance, il n'est pas possible à l'aide d'C99-spécifié de préprocesseur fonctionnalités, vous devez utiliser une extension de la langue. clang et de la cci, ont adopté la présente condition de l'extension, mais MSVC n'a pas.De retour en 2001, j'ai écrit jusqu'à la GCC extension de normalisation (et l'extension qui vous permet d'utiliser un autre nom que
__VA_ARGS__
pour le reste-paramètre) dans document N976, mais que reçu aucune réponse de la part du comité; je ne sais même pas si quelqu'un à le lire. En 2016 il a été proposé de nouveau en N2023, et j'encourage tout le monde qui sait comment cette proposition va à laissez-nous savoir dans les commentaires.comp.std.c
mais j'ai été incapable de trouver tout dans Google Groupes seulement maintenant; il n'a certainement jamais eu toute l'attention de la commission (ou si elle l'a fait, jamais personne ne m'a dit à ce sujet).Il y a un argument de comptage astuce que vous pouvez utiliser.
Ici est un standard conforme façon de mettre en œuvre la deuxième
BAR()
exemple dans jwd la question:Cette astuce est utilisée pour:
__VA_ARGS__
Explication
La stratégie est de séparer
__VA_ARGS__
dans le premier argument et le reste (le cas échéant). Cela permet d'insérer des trucs après le premier argument, mais avant la deuxième (si présent).FIRST()
Cette macro simplement se développe pour le premier argument, en rejetant le reste.
La mise en œuvre est simple. Le
throwaway
argument assure queFIRST_HELPER()
reçoit deux arguments, ce qui est nécessaire parce que le...
besoin d'au moins une. Avec un argument, il se développe comme suit:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Avec deux ou plus de deux, elle se développe comme suit:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
firstarg
REST()
Cette macro s'étend à tout, mais le premier argument (y compris la virgule après le premier argument, si il n'y a plus d'un argument).
La mise en œuvre de cette macro est beaucoup plus compliqué. La stratégie générale est de compter le nombre d'arguments (un ou plus d'un) et puis développez soit
REST_HELPER_ONE()
(si un seul argument donné) ouREST_HELPER_TWOORMORE()
(si deux ou plus de deux arguments).REST_HELPER_ONE()
simplement étend à rien, il n'y a pas d'arguments après la première, de sorte que les arguments restants est l'ensemble vide.REST_HELPER_TWOORMORE()
est aussi simple, il se développe à une virgule suivie par tout, sauf le premier argument.Les arguments sont comptées à l'aide de la
NUM()
macro. Cette macro s'étend àONE
si un seul argument est fourni,TWOORMORE
si, entre deux et neuf arguments sont donnés, et des pauses si 10 ou plusieurs arguments sont donnés (parce qu'il se développe à la 10e argument).La
NUM()
macro utilise laSELECT_10TH()
macro pour déterminer le nombre d'arguments. Comme son nom l'indique,SELECT_10TH()
simplement se développe pour sa 10e argument. En raison de l'ellipse,SELECT_10TH()
doit être transmis au moins 11 des arguments (la norme dit qu'il doit y avoir au moins un argument pour les points de suspension). C'est pourquoiNUM()
passethrowaway
comme le dernier argument (sans elle, passant un argument pourNUM()
seraient seuls 10 arguments passés àSELECT_10TH()
, qui constituerait une violation de la norme).Sélection de
REST_HELPER_ONE()
ouREST_HELPER_TWOORMORE()
est fait par concaténation deREST_HELPER_
avec l'expansion deNUM(__VA_ARGS__)
dansREST_HELPER2()
. Notez que le but deREST_HELPER()
est de s'assurer queNUM(__VA_ARGS__)
est complètement développé avant d'être concaténé avecREST_HELPER_
.Expansion avec un argument qui va 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)
Expansion avec deux arguments ou plus va 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
Pas une solution générale, mais dans le cas de printf vous pourriez ajouter un saut de ligne comme:
Je crois qu'il ignore tout supplémentaire des arguments qui ne sont pas référencées dans la chaîne de format. Donc, vous pourriez même obtenir loin avec:
Je ne peux pas croire C99 a été approuvé sans un moyen standard pour ce faire. AFAICT le problème existe dans C++11 trop.
Il y a un moyen de gérer ce cas particulier en utilisant quelque chose comme Coup de pouce.Préprocesseur. Vous pouvez utiliser BOOST_PP_VARIADIC_SIZE pour vérifier la taille de la liste d'arguments, et puis conditionaly étendre à une autre macro. Le seul inconvénient c'est qu'il ne peut pas distinguer entre 0 et 1 argument, et la raison pour laquelle cela devient évident une fois que vous considérez les points suivants:
Le vide macro argument de la liste se compose en fait d'un argument qui se trouve être vide.
Dans ce cas, nous avons la chance depuis votre macro souhaitée a toujours au moins 1 argument, nous pouvons mettre en œuvre que les deux "surcharge" des macros:
Et puis une autre macro pour basculer entre eux, tels que:
ou
Celui qui vous est le plus lisible (je préfère la première car il vous donne une forme générale pour la surcharge des macros sur le nombre d'arguments).
Il est également possible de le faire avec une seule macro par l'accès et de la mutation de la variable d'arguments, mais c'est moins lisible, et est très spécifique à ce problème:
Aussi, pourquoi n'est-il pas BOOST_PP_ARRAY_ENUM_TRAILING? Il serait de rendre cette solution beaucoup moins horrible.
Edit: Bon, voici une BOOST_PP_ARRAY_ENUM_TRAILING, et une version qui l'utilise (c'est maintenant ma solution préférée):
BOOST_PP_VARIADIC_SIZE()
utilise le même argument de comptage truc que j'ai documenté dans ma réponse, et a la même limite (il va se casser si vous passez plus d'un certain nombre d'arguments).J'ai rencontré un problème similaire récemment, et je crois qu'il y a une solution.
L'idée essentielle est qu'il y a une façon d'écrire une macro
NUM_ARGS
pour compter le nombre d'arguments d'une variadic macro est donnée. Vous pouvez utiliser une variation deNUM_ARGS
de construireNUM_ARGS_CEILING2
, qui peut vous dire si un variadic macro est donnée 1 argument ou 2-ou-plus d'arguments. Ensuite, vous pouvez écrire votreBar
macro afin qu'il utiliseNUM_ARGS_CEILING2
etCONCAT
pour envoyer ses arguments à l'un des deux les macros d'assistance: l'un qui s'attend à exactement 1 argument, et un autre qui s'attend à un nombre variable d'arguments plus grand que 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 mis en œuvre dans la manière habituelle. Un petit indice, si le ci-dessus vous semble confus: l'objectif de CONCAT il est de l'étendre à une autre macro "appel".
Noter que NUM_ARGS lui-même n'est pas utilisé. J'ai juste inclus pour illustrer l'astuce de base ici. Voir Jens Gustedt de P99 blog pour un bon traitement.
Deux remarques:
NUM_ARGS est limité dans le nombre d'arguments qu'elle gère. Le mien
ne peut gérer jusqu'à 20, bien que le nombre est totalement arbitraire.
NUM_ARGS, comme indiqué, est un piège pour qu'elle retourne 1 lorsque 0 arguments. L'essentiel, c'est que NUM_ARGS est techniquement comptage [virgules + 1], et non pas des arguments. Dans ce
cas particulier, il fonctionne réellement pour notre
avantage. _UNIMPLEMENTED1 va gérer un jeton vide un peu fine
et il nous évite d'avoir à écrire _UNIMPLEMENTED0. Gustedt a un
solution de contournement pour que, bien que je ne l'ai pas utilisé et je ne sais pas si cela fonctionne pour ce que nous faisons ici.
NUM_ARGS
mais ne l'utilisez pas. 2. Quel est le but deUNIMPLEMENTED
? 3. Vous n'avez jamais résoudre l'exemple de problème dans la question. 4. La marche grâce à l'expansion d'une étape à la fois de montrer comment il fonctionne et expliquer le rôle de chaque accompagnateur de macro. 5. Discuter de 0 arguments est de distraire; l'OP a une question sur la conformité aux normes et 0 arguments est interdit (C99 6.10.3p4). 6. L'étape 1.5? Pourquoi ne pas l'étape 2? 7. "Étapes" implique des actions qui se produisent de façon séquentielle; c'est juste le code.CONCAT()
-- ne présumez pas que les lecteurs de savoir comment il fonctionne.Très simple macro que j'utilise pour l'impression de débogage:
Peu importe le nombre d'arguments passés à DBG il n'y a pas c99 avertissement.
Le truc, c'est
__DBG_INT
l'ajout d'un mannequin param donc...
aura toujours au moins un argument et c99 est satisfait.C'est la version simplifiée que j'utilise. Elle est basée sur les grands des techniques des autres réponses ici, de sorte que de nombreux accessoires pour eux:
Que c'est.
Comme avec d'autres solutions c'est limité au nombre d'arguments de la macro. À l'appui de plus, ajouter plus de paramètres à
_SELECT
, et plusN
arguments. L'argument des noms de compte à rebours (au lieu de vers le haut) pour servir comme un rappel que le comptageSUFFIX
argument est fourni dans l'ordre inverse.Cette solution traite 0 arguments comme si il est à 1 argument. Donc
BAR()
nominalement "œuvres", parce qu'il se développe à_SELECT(_BAR,,N,N,N,N,1)()
, qui s'étend à_BAR_1()()
, qui s'étend àprintf("\n")
.Si vous le souhaitez, vous pouvez faire preuve de créativité avec l'utilisation de
_SELECT
et de fournir des macros différentes pour différents nombre d'arguments. Par exemple, nous avons ici un JOURNAL de macro qui prend un 'niveau' argument avant de le format. Si le format est manquant, il enregistre "(pas de message)", si il est à seulement 1 argument, il va se connecter à travers "%s", sinon il va traiter de l'argument de format comme format de printf pour le reste des arguments.Dans votre situation (au moins 1 argument présent, jamais 0), vous pouvez définir
BAR
commeBAR(...)
, utilisez Jens Gustedt de l'HAS_COMMA(...)
pour détecter une virgule et de la répartition à laBAR0(Fmt)
ouBAR1(Fmt,...)
en conséquence.Ce:
compile avec
-pedantic
sans avertissement.C (gcc), 762 octets
Essayer en ligne!
Suppose:
A
~G
(peut renommer hard_collide ceux)no arg contain comma
limitation peut être contournée par la vérification de multi après quelques passes, maisno bracket
toujours làLa solution standard est d'utiliser
FOO
au lieu deBAR
. Il y a un peu étrange cas de l'argument de la réorganisation il ne peut probablement pas faire pour vous (même si je parie que quelqu'un peut venir avec des hacks pour démonter et remonter__VA_ARGS__
conditionnelle basée sur le nombre d'arguments dans la!) mais, en général, à l'aide deFOO
"habituellement" fonctionne, tout simplement.