2012-12-12 35 views
72

Supponiamo di avere una macro come questovirgola in C/C++ macro

#define FOO(type,name) type name 

che potremmo usare come

FOO(int, int_var); 

Ma non sempre nel modo più semplice che:

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

Naturalmente potremmo fare:

typedef std::map<int, int> 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?

+0

Immagino che tu debba sfuggire ai personaggi con un significato per renderli letterali. – Jite

+0

Almeno in C++, puoi inserire un typedef ovunque, quindi non sono sicuro del perché tu dici che deve essere "in anticipo". –

risposta

78

Perché le parentesi angolari possono anche rappresentare (o verificarsi) il confronto operatori <, >, <= e >=, l'espansione macro non può ignorare le virgole tra parentesi angolari come fa tra parentesi. (Questo è un problema anche per parentesi quadre e graffe, anche se quelli normalmente si verificano come coppie bilanciate.) È possibile racchiudere l'argomento macro in parentesi:

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

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

Un bel trucco per aggirare questo è che in C++, è possibile estrarre un TypeName da un nome di tipo tra parentesi usando un tipo di funzione:

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); 

Perché la formazione di tipi di funzione ignora parentesi in più, è possibile 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, naturalmente, questo non è necessario perché i nomi di tipo non possono contenere virgole fuori parentesi. Così, per una macro cross-language si può scrivere:

#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 
+0

Questo è fantastico. Ma come hai saputo di questo? Ho provato tantissimi trucchi e non ho mai nemmeno pensato che un tipo di funzione avrebbe risolto il problema. –

+0

@WilliamCustode come ricordo, stavo studiando la grammatica dei tipi di funzione e delle dichiarazioni di funzione con riferimento al problema di analisi più irritante, quindi è stato un caso che io sapessi che le parentesi ridondanti potrebbero essere applicate a un tipo in quel contesto. – ecatmur

+0

Ho trovato un problema con questo metodo quando si lavora con i modelli.Diciamo che il codice che volevo era questo: 'template void SomeFunc (FOO (std :: map ) elemento) {}' Se applico questa soluzione qui, le strutture dietro la macro diventano tipi dipendenti, e il tipo di prefisso del nome è ora richiesto. Puoi aggiungerlo, ma la deduzione di tipo è stata interrotta, quindi ora devi elencare manualmente gli argomenti di tipo per chiamare la funzione. Ho finito col usare il metodo di Temple per definire una macro per la virgola. Potrebbe non sembrare carino, ma ha funzionato perfettamente. –

38

Se il preprocessore supporta le macro variadic:

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

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

In caso contrario, è 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<int, int>), map_var); 
+0

Oh, Dio ... Perché? Perché non racchiudere tra parentesi? –

+11

@VladLazarenko: Perché non puoi sempre mettere tra parentesi pezzi di codice arbitrari. In particolare, non puoi mettere parentesi attorno al nome del tipo in un dichiaratore, che è esattamente ciò che diventa questo argomento. –

+1

... e anche perché potresti essere in grado di modificare la macro _definizione_ e non tutte le posizioni che la chiamano (che potrebbero non essere sotto il tuo controllo, o potrebbero essere distribuite su migliaia di file, ecc.). Ciò si verifica, ad esempio, quando si aggiunge una macro per assumere le funzioni da una funzione con nome simile. – BeeOnRope

2

Ci sono almeno due modi per farlo. In primo luogo, è possibile definire una macro che prende più argomenti:

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

se si fa questo si potrebbe scoprire che si finisce per definire più macro per gestire più argomenti.

In secondo luogo, si può mettere tra parentesi l'argomento:

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

se si fa questo si potrebbe scoprire che le parentesi aggiuntive rovinare la sintassi del risultato.

+0

Per la prima soluzione, ciascuna macro dovrà avere un nome diverso, poiché le macro non sovraccaricano. E per il secondo, se stai trasmettendo un nome di tipo, ci sono ottime possibilità che venga usato per dichiarare una variabile (o un typedef), quindi le parentesi causeranno problemi. –

1

La semplice risposta è che non è possibile. Questo è un effetto collaterale della scelta di <...> per gli argomenti del modello; i modelli < e > vengono visualizzati anche in contesti sbilanciati, pertanto il meccanismo macro non può essere esteso per gestirli come gestisce le parentesi. (Alcuni membri del comitato avevano sostenuto un diverso token, ad esempio (^...^), ma non erano in grado di convincere la maggior parte dei problemi utilizzando <...>.)

2

Ciò è possibile con P99:

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

Il codice strisce sopra in modo efficace solo l'ultima virgola nella lista degli argomenti. Verificare con clang -E (P99 richiede un compilatore C99).

81

Se non è possibile usare le parentesi e non ti piace la soluzione SINGLE_ARG di Mike, basta definire una virgola:

#define COMMA , 

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

Questo aiuta anche se si vuole stringa i alcuni degli argomenti di macro, come in

#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); 
} 

che stampa std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

+9

#define COMMA wow, mi hai appena salvato ORE di lavoro ... perché non ci ho pensato anni fa. Grazie per aver condiviso questa idea. Questo mi consente anche di creare macro che configurano funzioni con diversi argomenti. – moliad

+14

Più 1 per l'horror – namezero

+0

non funziona se si vuole stringere l'argomento ... – kiw

12

Basta definire FOO come

#define UNPACK(...) __VA_ARGS__ 

#define FOO(type, name) UNPACK type name 

Poi invocarla sempre con parentesi attorno al argomento di tipo, per esempio

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

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

+0

Non sono sicuro del motivo per cui questo è così giù, è una soluzione molto più bella di Mike Seymours. È veloce e semplice e completamente nascosto all'utente. – iFreilicht

+1

@iFreilicht: è stato pubblicato poco più di un anno dopo. ;-) –

+1

E perché anche è difficile capire come e perché funziona – VinGarcia