J'essaie d'écrire un programme où les noms de certaines fonctions dépendent de la valeur d'une certaine variable macro avec une macro comme celle-ci:
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
int NAME(some_function)(int a);
Malheureusement, la macro NAME()
transforme cela en
int some_function_VARIABLE(int a);
plutôt que
int some_function_3(int a);
c'est donc clairement la mauvaise façon de procéder. Heureusement, le nombre de valeurs différentes possibles pour VARIABLE est petit, donc je peux simplement faire un #if VARIABLE == n
et lister tous les cas séparément, mais je me demandais s'il existe un moyen intelligent de le faire.
#define A 0 \n #define M a ## A
: en avoir deux##
n'est pas la clé.Réponses:
Préprocesseur C standard
Deux niveaux d'indirection
Dans un commentaire sur une autre réponse, Cade Roux a demandé pourquoi cela nécessite deux niveaux d'indirection. La réponse désinvolte est que c'est ainsi que la norme l'exige; vous avez tendance à trouver que vous avez également besoin de l'astuce équivalente avec l'opérateur stringizing.
La section 6.10.3 de la norme C99 couvre le «remplacement de macros» et 6.10.3.1 la «substitution d'arguments».
Dans l'invocation
NAME(mine)
, l'argument est «mien»; il est entièrement étendu au «mien»; il est ensuite remplacé dans la chaîne de remplacement:Maintenant, la macro EVALUATOR est découverte, et les arguments sont isolés comme «mien» et «VARIABLE»; ce dernier est ensuite entièrement développé en '3', et remplacé dans la chaîne de remplacement:
Son fonctionnement est couvert par d'autres règles (6.10.3.3 'L'opérateur ##'):
Ainsi, la liste de remplacement contient
x
suivi de##
et également##
suivi dey
; donc nous avons:et l'élimination des
##
jetons et la concaténation des jetons de chaque côté combine «mien» avec «_» et «3» pour donner:C'est le résultat souhaité.
Si nous regardons la question originale, le code était (adapté pour utiliser 'mine' au lieu de 'some_function'):
L'argument de NAME est clairement «le mien» et il est entièrement développé.
En suivant les règles du 6.10.3.3, on trouve:
qui, lorsque les
##
opérateurs sont éliminés, correspond à:exactement comme indiqué dans la question.
Préprocesseur C traditionnel
Robert Rüger demande :
Peut-être, et peut-être pas - cela dépend du préprocesseur. L'un des avantages du préprocesseur standard est qu'il dispose de cette fonctionnalité qui fonctionne de manière fiable, alors qu'il existait différentes implémentations pour les préprocesseurs pré-standard. Une condition est que lorsque le préprocesseur remplace un commentaire, il ne génère pas d'espace comme le préprocesseur ANSI est tenu de le faire. Le préprocesseur GCC (6.3.0) C satisfait à cette exigence; le préprocesseur Clang de XCode 8.2.1 ne le fait pas.
Quand cela fonctionne, cela fait le travail (
x-paste.c
):Notez qu'il n'y a pas d'espace entre
fun,
etVARIABLE
- c'est important car s'il est présent, il est copié dans la sortie et vous vous retrouvez avecmine_ 3
comme nom, ce qui n'est pas syntaxiquement valide, bien sûr. (Maintenant, s'il vous plaît, puis-je avoir mes cheveux en arrière?)Avec GCC 6.3.0 (en cours d'exécution
cpp -traditional x-paste.c
), j'obtiens:Avec Clang de XCode 8.2.1, j'obtiens:
Ces espaces gâchent tout. Je note que les deux préprocesseurs sont corrects; différents préprocesseurs pré-standard présentaient les deux comportements, ce qui faisait du collage de jetons un processus extrêmement ennuyeux et peu fiable lors de la tentative de portage du code. La norme avec la
##
notation simplifie radicalement cela.Il pourrait y avoir d'autres façons de faire cela. Cependant, cela ne fonctionne pas:
GCC génère:
Proche, mais pas de dés. YMMV, bien sûr, en fonction du préprocesseur pré-standard que vous utilisez. Franchement, si vous êtes coincé avec un préprocesseur qui ne coopère pas, il serait probablement plus simple de prendre des dispositions pour utiliser un préprocesseur C standard à la place du préprocesseur pré-standard (il existe généralement un moyen de configurer le compilateur de manière appropriée) que de passer beaucoup de temps à essayer de trouver un moyen de faire le travail.
la source
cpp -traditional
. Notez qu'il n'y a pas de réponse définitive - cela dépend du préprocesseur que vous avez.Honnêtement, vous ne voulez pas savoir pourquoi cela fonctionne. Si vous savez pourquoi cela fonctionne, vous deviendrez ce gars au travail qui connaît ce genre de choses, et tout le monde viendra vous poser des questions. =)
Edit: si vous voulez vraiment savoir pourquoi cela fonctionne, je publierai volontiers une explication, en supposant que personne ne me batte.
la source