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?
Étant donné que les crochets angulaires peuvent également représenter (ou apparaître dans) les opérateurs de comparaison <
, >
, <=
et >=
, l'expansion de macro ne peut pas ignorer les virgules entre crochets comme elle le fait entre parenthèses. (C'est également un problème pour les crochets et les accolades, même si ceux-ci se produisent généralement sous forme de paires équilibrées.) Vous pouvez placer l'argument macro entre parenthèses:
FOO((std::map<int, int>), map_var);
Le problème est alors que le paramètre reste entre parenthèses à l'intérieur de l'expansion de macro, ce qui empêche sa lecture en tant que 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 à partir 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 de types de fonction ignore les parenthèses supplémentaires, vous pouvez utiliser cette macro avec ou sans parenthèses où le nom du type ne comprend pas de virgule:
FOO((int), int_var);
FOO(int, int_var2);
En C, bien sûr, cela n'est pas nécessaire car les noms de type ne peuvent pas contenir de virgules en dehors des parenthèses. Ainsi, 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
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 est également utile 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"
.
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);
Définissez simplement FOO
comme
#define UNPACK( ... ) __VA_ARGS__
#define FOO( type, name ) UNPACK type name
Ensuite, invoquez-le toujours avec des parenthèses autour de l'argument type, par ex.
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 macro.
C'est possible avec P99 :
#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()
Le code ci-dessus supprime effectivement uniquement la dernière virgule de la liste des arguments. Vérifier avec clang -E
(P99 nécessite un compilateur C99).
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 pouvez constater 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.
La réponse simple est que vous ne pouvez pas. Ceci est un effet secondaire du choix de <...>
pour les arguments de modèle; le <
et >
apparaissent également dans des contextes déséquilibrés, de sorte que le mécanisme de macro n'a pas pu être étendu pour les gérer comme il gère les parenthèses. (Certains membres du comité avaient plaidé pour un autre jeton, disons (^...^)
, mais ils n'ont pas réussi à convaincre la majorité des problèmes en utilisant <...>
.)