Virgola nella macro C / C ++


103

Supponiamo di avere una macro come questa

#define FOO(type,name) type name

Quale potremmo usare come

FOO(int, int_var);

Ma non sempre così semplicemente:

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

Ovviamente potremmo fare:

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

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


Immagino che devi sfuggire ai personaggi con un significato per renderli letterali.
Jite

Almeno in C ++, puoi mettere un typedef ovunque, quindi non sono sicuro del motivo per cui dici che deve essere "in anticipo".
Vaughn Cato

Risposte:


108

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

FOO((std::map<int, int>), 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 risolvere questo problema è che in C ++ puoi estrarre un nome di tipo 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);

Poiché la formazione dei tipi di funzione ignora le parentesi aggiuntive, puoi 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 fuori parentesi. Quindi, per una macro multilingue puoi 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

Questo e spettacolare. Ma come l'hai scoperto? Ho provato tantissimi trucchi e non ho mai nemmeno pensato che un tipo di funzione avrebbe risolto il problema.
Will Custode

@WilliamCustode, per quanto ricordo, stavo studiando la grammatica dei tipi di funzione e delle dichiarazioni di funzione con riferimento al problema di analisi più fastidioso, quindi è stato un caso che fossi consapevole che le parentesi ridondanti potevano essere applicate a un tipo in quel contesto.
ecatmur

Ho riscontrato un problema con questo metodo quando lavoravo con i modelli. Diciamo che il codice che volevo era questo: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}se applico questa soluzione qui, le strutture dietro la macro diventano tipi dipendenti e il prefisso del nome del tipo è ora richiesto per il tipo. Puoi aggiungerlo, ma la deduzione del tipo è stata interrotta, quindi ora devi elencare manualmente gli argomenti del tipo per chiamare la funzione. Ho finito per utilizzare il metodo di temple per definire una macro per la virgola. Potrebbe non sembrare così carino, ma ha funzionato perfettamente.
Roger Sanders

Un piccolo problema sulla risposta: afferma che le virgole vengono ignorate all'interno [] e {}, non lo sono, funziona solo con ()tristezza. Vedi: Tuttavia, non sono richieste parentesi quadre o parentesi graffe per bilanciare ...
VinGarcia

Sfortunatamente questo non funziona in MSVC: godbolt.org/z/WPjYW8 . Sembra che MSVC non consenta l'aggiunta di più parentesi e non riesca ad analizzarlo. Una soluzione che non è elegante, ma più veloci (meno istanze di modello) è quello di avvolgere l'argomento da virgole ed in una macro involucro: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Il passaggio di argomenti è ora facilmente possibile anche attraverso più macro e per i tipi semplici è possibile omettere il PROTECT. Tuttavia, i tipi di funzione diventano puntatori a funzione quando valutati in questo modo
Flamefire

119

Se non puoi usare le parentesi e non ti piace la soluzione SINGLE_ARG di Mike, definisci semplicemente un COMMA:

#define COMMA ,

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

Questo aiuta anche se vuoi stringere alcuni degli argomenti 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".


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

28
Più 1 per l'orrore
namezero

1
@kiw Se tu #define STRVX(...) STRV(__VA_ARGS__)e #define STRV(...) # __VA_ARGS__, allora std::cout << STRV(type<A COMMA B>) << std::endl;stamperai type<A COMMA B>e std::cout << STRVX(type<A COMMA B>) << std::endl;stamperà type<A , B>. ( STRVsta per "variadic stringify" e STRVXsta per "extended variadic stringify".)
not-a-user

1
@ non-utente sì, ma con le macro variadiche non hai bisogno della COMMAmacro in primo luogo. Questo è ciò con cui sono finito.
kiw

Non lo userei mai, ma +1 per essere divertente.
Rafael Baptista

58

Se il tuo preprocessore supporta le macro variadiche:

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

FOO(SINGLE_ARG(std::map<int, int>), 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<int, int>), map_var);

Oh, dio ... Perché? Perché non racchiudere tra parentesi?

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

2
... e anche perché potresti essere in grado di modificare solo la definizione della macro e non tutti i luoghi che la chiamano (che potrebbero non essere sotto il tuo controllo, o potrebbero essere distribuiti su migliaia di file, ecc.). Ciò si verifica, ad esempio, quando si aggiunge una macro per assumere compiti da una funzione con lo stesso nome.
BeeOnRope

32

Basta definire FOOcome

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Quindi invocalo sempre tra parentesi attorno all'argomento del tipo, ad es

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

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


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

3
@iFreilicht: è stato pubblicato poco più di un anno dopo. ;-)
Saluti e hth. - Alf

5
E perché anche è difficile capire come e perché funzioni
VinGarcia

@VinGarcia, puoi spiegare perché / come funziona? Perché le parentesi sono necessarie quando lo si chiama? Cosa UNPACKfare se usato in questo modo ) UNPACK type name? Perché typeottiene correttamente il tipo quando viene utilizzato ) UNPACK type name? Cosa diavolo sta succedendo qui?
utente

Nessun utente @, forse Cheers e hth possono risponderti
VinGarcia

4

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.

Secondo, puoi mettere parentesi attorno all'argomento:

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

se lo fai potresti scoprire che le parentesi extra rovinano la sintassi del risultato.


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

4

Questo è possibile con P99 :

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

Il codice precedente rimuove efficacemente solo l'ultima virgola nell'elenco degli argomenti. Verificare con clang -E(P99 richiede un compilatore C99).


3

La semplice risposta è che non puoi. Questo è un effetto collaterale della scelta di <...>per gli argomenti del modello; l' <e >appaiono anche in contesti non bilanciati in modo che il meccanismo di macro non potrebbe essere esteso a gestirli come gestisce parentesi. (Alcuni membri del comitato avevano sostenuto per un token diverso, ad esempio (^...^), ma non sono stati in grado di convincere la maggior parte dei problemi nell'uso <...>.)


2
(^...^)questa è una faccia felice :)
CygnusX1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.