Virgule dans une macro C / C ++


104

Disons que nous avons une macro comme celle-ci

#define FOO(type,name) type name

Que nous pourrions utiliser comme

FOO(int, int_var);

Mais pas toujours aussi simplement que ça:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Bien sûr, nous pourrions faire:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

ce qui n'est pas très ergonomique. Les incompatibilités de type Plus doivent être traitées. Une idée comment résoudre ce problème avec une macro?


Je suppose que vous devez échapper aux personnages avec une signification pour en faire des littéraux.
Jite

Au moins en C ++, vous pouvez mettre un typedef n'importe où, donc je ne sais pas pourquoi vous dites qu'il doit être «au préalable».
Vaughn Cato

Réponses:


109

Parce que les supports d'angle peuvent également représenter (ou se produire dans) les opérateurs de comparaison <, >, <=et >=, l' expansion macro ne peuvent pas ignorer les virgules à l' intérieur crochets comme il le fait entre parenthèses. (C'est également un problème pour les crochets et les accolades, même si ceux-ci se présentent généralement sous forme de paires équilibrées.) Vous pouvez mettre l'argument de macro entre parenthèses:

FOO((std::map<int, int>), map_var);

Le problème est alors que le paramètre reste entre parenthèses dans le développement de la macro, ce qui l'empêche d'être lu comme un type dans la plupart des contextes.

Une astuce intéressante pour contourner ce problème est qu'en C ++, vous pouvez extraire un nom de type d'un nom de type entre parenthèses à l'aide d'un type de fonction:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Étant donné que la formation des types de fonction ignore les parenthèses supplémentaires, vous pouvez utiliser cette macro avec ou sans parenthèses où le nom du type n'inclut pas de virgule:

FOO((int), int_var);
FOO(int, int_var2);

En C, bien sûr, ce n'est pas nécessaire car les noms de type ne peuvent pas contenir de virgules en dehors des parenthèses. Donc, pour une macro multilingue, vous pouvez écrire:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif

C'est génial. Mais comment avez-vous appris cela? J'ai essayé des tonnes d'astuces et je n'ai même jamais pensé qu'un type de fonction résoudrait le problème.
Will Custode le

@WilliamCustode si je me souviens bien, j'avais étudié la grammaire des types de fonctions et des déclarations de fonctions en référence au problème d'analyse le plus épineux, il était donc fortuit que je sache que des parenthèses redondantes pouvaient être appliquées à un type dans ce contexte.
ecatmur le

J'ai trouvé un problème avec cette méthode lors de l'utilisation de modèles. Disons que le code que je voulais était le suivant: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}si j'applique cette solution ici, les structures derrière la macro deviennent des types dépendants, et le préfixe du nom de type est maintenant requis sur le type. Vous pouvez l'ajouter, mais la déduction de type a été interrompue, vous devez donc maintenant lister manuellement les arguments de type pour appeler la fonction. J'ai fini par utiliser la méthode du temple pour définir une macro pour la virgule. Cela n'a peut-être pas l'air aussi joli, mais cela a parfaitement fonctionné.
Roger Sanders

Un petit problème sur la réponse: il indique que les virgules sont ignorées à l'intérieur [] et {}, elles ne le sont pas, cela ne fonctionne que ()malheureusement. Voir: Cependant, il n'y a pas d'exigence pour les crochets ou les accolades pour équilibrer ...
VinGarcia

Malheureusement, cela ne fonctionne pas dans MSVC : godbolt.org/z/WPjYW8 . Il semble que MSVC n'autorise pas l'ajout de plusieurs parenthèses et ne parvient pas à les analyser. Une solution qui n'est pas aussi élégante mais (instanciations moins de modèle) plus rapide est d'envelopper l'argument par des virgules ed dans une macro d'emballage: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Le passage d'arguments est désormais facilement possible même via plusieurs macros et pour les types simples, vous pouvez ignorer PROTECT. Cependant, les types de fonction deviennent des pointeurs de fonction lorsqu'ils sont évalués comme ceci
Flamefire

119

Si vous ne pouvez pas utiliser de parenthèses et que vous n'aimez pas la solution SINGLE_ARG de Mike, définissez simplement un COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

Cela aide également si vous souhaitez stringifier certains des arguments de macro, comme dans

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

qui imprime std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".


16
#define COMMA wow, vous venez de m'économiser des HEURES de travail ... pourquoi n'y ai-je pas pensé il y a des années. Merci d'avoir partagé cette idée. Cela me permet même de créer des macros dont la configuration fonctionne avec différents nombres d'arguments.
moliad

28
Plus 1 pour l'horreur
namezero

1
@kiw Si vous #define STRVX(...) STRV(__VA_ARGS__)et #define STRV(...) # __VA_ARGS__, puis std::cout << STRV(type<A COMMA B>) << std::endl;imprimera type<A COMMA B>et std::cout << STRVX(type<A COMMA B>) << std::endl;imprimera type<A , B>. ( STRVest pour "variadic stringify", et STRVXest pour "étendu variadic stringify".)
not-a-user

1
@ not-a-user oui, mais avec les macros variadiques, vous n'avez pas besoin de la COMMAmacro en premier lieu. C'est ce avec quoi j'ai fini.
kiw

Je n'utiliserais jamais ça, mais +1 pour être hilarant.
Rafael Baptista

58

Si votre préprocesseur prend en charge les macros variadiques:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

Sinon, c'est un peu plus fastidieux:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);

Oh, mon Dieu ... Pourquoi? Pourquoi ne pas simplement mettre entre parenthèses?

15
@VladLazarenko: Parce que vous ne pouvez pas toujours mettre des morceaux de code arbitraires entre parenthèses. En particulier, vous ne pouvez pas mettre de parenthèses autour du nom du type dans un déclarateur, ce qui est exactement ce que devient cet argument.
Mike Seymour

2
... et aussi parce que vous ne pourrez peut-être modifier que la définition de la macro et pas tous les endroits qui l'appellent (qui peuvent ne pas être sous votre contrôle, ou peuvent être réparties sur des milliers de fichiers, etc.). Cela se produit, par exemple, lors de l'ajout d'une macro pour prendre en charge les tâches d'une fonction portant le même nom.
BeeOnRope

32

Définissez simplement FOOcomme

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Puis invoquez-le toujours avec des parenthèses autour de l'argument type, par exemple

FOO( (std::map<int, int>), map_var );

Il peut bien sûr être une bonne idée d'illustrer les invocations dans un commentaire sur la définition de la macro.


Je ne sais pas pourquoi c'est si bas, c'est une solution beaucoup plus agréable que Mike Seymours. C'est rapide et simple et complètement caché à l'utilisateur.
iFreilicht

3
@iFreilicht: Il a été publié un peu plus d'un an plus tard. ;-)
Bravo et hth. - Alf

5
Et parce qu'il est également difficile de comprendre comment et pourquoi cela fonctionne
VinGarcia

@VinGarcia, vous pouvez expliquer pourquoi / comment ça marche? Pourquoi les parenthèses sont nécessaires lors de son appel? Que UNPACKfaire quand on l'utilise comme ça ) UNPACK type name? Pourquoi typeobtient correctement le type lorsqu'il est utilisé ) UNPACK type name? Mais qu'est-ce qui se passe ici?
utilisateur

Non @user, peut-être que Cheers et hth peuvent vous répondre
VinGarcia

4

Il existe au moins deux façons de procéder. Tout d'abord, vous pouvez définir une macro qui prend plusieurs arguments:

#define FOO2(type1, type2, name) type1, type2, name

si vous faites cela, vous constaterez peut-être que vous finissez par définir plus de macros pour gérer plus d'arguments.

Deuxièmement, vous pouvez mettre des parenthèses autour de l'argument:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

si vous faites cela, vous constaterez peut-être que les parenthèses supplémentaires gâchent la syntaxe du résultat.


Pour la première solution, chaque macro devra avoir un nom différent, car les macros ne se surchargent pas. Et pour le second, si vous passez un nom de type, il y a de très bonnes chances qu'il soit utilisé pour déclarer une variable (ou un typedef), donc les parenthèses poseront des problèmes.
James Kanze

4

Ceci est possible avec P99 :

#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()

Le code ci-dessus supprime en fait uniquement la dernière virgule de la liste d'arguments. Vérifiez avec clang -E(P99 nécessite un compilateur C99).


3

La réponse simple est que vous ne pouvez pas. C'est un effet secondaire du choix des <...>arguments de modèle; les <et >apparaissent également dans des contextes déséquilibrés, de sorte que le mécanisme de macro ne peut pas être étendu pour les gérer comme il gère les parenthèses. (Certains membres du comité avaient plaidé pour un jeton différent, par exemple (^...^), mais ils n'ont pas été en mesure de convaincre la majorité des problèmes d'utilisation <...>.)


2
(^...^)c'est un visage heureux :)
CygnusX1
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.