Virgola in macro C / C ++

Diciamo che abbiamo una macro come questa

#define FOO(type,name) type name 

Che potremmo usare come

 FOO(int, int_var); 

Ma non sempre così semplicemente:

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

Certo che potremmo fare:

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

che non è molto ergonomico. Devono essere affrontate anche le incompatibilità di tipo. Qualche idea su come risolvere questo problema con la macro?

Poiché le parentesi angolari possono anche rappresentare (o verificarsi) gli operatori di confronto < , > , <= e >= , l'espansione macro non può ignorare le virgole tra parentesi angolari come fa tra parentesi. (Questo è anche un problema per parentesi quadre e parentesi graffe, anche se solitamente si presentano come coppie bilanciate.) È ansible racchiudere l'argomento macro tra parentesi:

 FOO((std::map), map_var); 

Il problema è quindi che il parametro rimane tra parentesi all'interno dell'espansione della macro, il che impedisce che venga letto come un tipo nella maggior parte dei contesti.

Un bel trucco per ovviare a questo è che in C ++, è ansible estrarre un nome di tipo da un nome di tipo con parentesi utilizzando un tipo di funzione:

 template struct argument_type; template struct argument_type { typedef U type; }; #define FOO(t,name) argument_type::type name FOO((std::map), map_var); 

Poiché la formazione dei tipi di funzione ignora le parentesi aggiuntive, è ansible utilizzare questa macro con o senza parentesi in cui il nome del tipo non include una virgola:

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

In C, ovviamente, questo non è necessario perché i nomi dei tipi non possono contenere virgole al di fuori delle parentesi. Quindi, per una macro cross-language puoi scrivere:

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

Se non puoi utilizzare le parentesi e non ti piace la soluzione SINGLE_ARG di Mike, basta definire un COMMA:

 #define COMMA , FOO(std::map, map_var); 

Questo aiuta anche se vuoi stringificare alcuni degli argomenti macro, come in

 #include  #include  #include  #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, std::printf); } 

che stampa std::map has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE" .

Se il tuo preprocessore supporta macro variadic:

 #define SINGLE_ARG(...) __VA_ARGS__ #define FOO(type,name) type name FOO(SINGLE_ARG(std::map), map_var); 

Altrimenti, è un po ‘più noioso:

 #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), map_var); 

Basta definire FOO come

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

Quindi invocalo sempre con parentesi attorno all’argomento type, ad es

 FOO( (std::map), map_var ); 

Ovviamente può essere una buona idea esemplificare le invocazioni in un commento sulla definizione della macro.

Ci sono almeno due modi per farlo. Innanzitutto, puoi definire una macro che accetta più argomenti:

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

se lo fai potresti scoprire che finisci per definire più macro per gestire più argomenti.

In secondo luogo, puoi mettere parentesi attorno all’argomento:

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

se lo fai potresti scoprire che le parentesi aggiunte rovinano la syntax del risultato.

Questo è ansible con P99 :

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

Il codice sopra efficacemente rimuove solo l’ultima virgola nella lista degli argomenti. Controlla con clang -E (P99 richiede un compilatore C99).

La semplice risposta è che non puoi. Questo è un effetto collaterale della scelta di <...> per gli argomenti del modello; anche < e > appaiono in contesti sbilanciati, quindi non è ansible estendere il meccanismo macro per gestirli come gestisce le parentesi. (Alcuni membri del comitato avevano argomentato per un diverso token, ad esempio (^...^) , ma non erano in grado di convincere la maggior parte dei problemi usando <...> .)