2015-08-17 25 views
7

Sto utilizzando alcune macro di registrazione, che dovrebbero stampare le informazioni fornite dalla macro __PRETTY_FUNCTION__ e, se necessario, nome e valore di un massimo di due argomenti. Una versione semplificata del mio codice è simileMacro variabile senza argomenti

template<typename Value1, typename Value2> 
void Log(std::string const& function, 
     std::string const& variable_1 = "", Value1 value_1 = Value1(0), 
     std::string const& variable_2 = "", Value2 value_2 = Value2(0)) { 
    std::cout << function << " " 
       << variable_1 << " " << value_1 << " " 
       << variable_2 << " " << value_2 << std::endl; 
} 
#define LOG0() Log(__PRETTY_FUNCTION__) 
#define VARIABLE(value) #value, value 
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value)) 
#define LOG2(value, value1) Log(__PRETTY_FUNCTION__, VARIABLE(value), VARIABLE(value1)) 
#define LOG(arg0, arg1, arg2, arg, ...) arg 
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0) 
#define Debug(...) CHOOSE(__VA_ARGS__)(__VA_ARGS__) 

posso usare queste macro come

Debug(); 
int x = 0; 
Debug(x); 
int y = 1; 
Debug(x, y); 

Quando compilo questo codice con clang ho un bel uscita contenente classe e informazioni funzione così come nome e valore delle variabili. Ma ricevo anche l'avviso che il codice conforme allo standard non può avere zero argomenti variadici.

warning: token pasting of ',' and __VA_ARGS__ is a GNU extension [-Wgnu-zero-variadic-macro-arguments] 
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0) 
         ^
warning: must specify at least one argument for '...' parameter of variadic macro [-Wgnu-zero-variadic-macro-arguments] 
Debug();  

Gcc invece fallisce la compilazione con

error: expected primary-expression before ‘)’ token 
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value)) 
                  ^
Debug(); 

Ovviamente è pericoloso lavorare con zero argomenti variadic.

  1. C'è un modo per trasformare questo codice in codice conforme standard senza rimuovere la comodità di avere una sola macro che richiede zero o due argomenti?
  2. Se questo non è possibile, c'è un modo per fare anche gcc compilare questo codice?
+0

Il comportamento di ## __ VA_ARGS__ dipende dalla versione di CPP. Può essere semplice come aggiungere uno spazio bianco prima della virgola precedente ##: 'LOG (, ## __ VA_ARGS __, ...' – Pianosaurus

+0

Inoltre, le tue macro funzionano come sono per me in g ++ 4.9.2, eccetto che g ++ non è in grado di dedurre il tipo di modello dei valori quando non vengono forniti. Hai pubblicato un esempio completo o manca qualcosa? – Pianosaurus

risposta

6

La parte difficile di questo è distinguere tra Debug() e Debug(x). In entrambi i casi, si passa tecnicamente un singolo argomento alla macro Debug. Nel primo caso, la sequenza di token di quell'argomento è vuota e nel secondo caso contiene un singolo token. Questi casi possono essere distinti con a trick due to to Jens Gustedt.

Ecco il trucco:

#define COMMA_IF_PARENS(...) , 

osservi che COMMA_IF_PARENS X produce una virgola se X inizia con (...), e altrimenti espande ad una sequenza di token contenente virgole (primo livello) aggiuntivi. Allo stesso modo, COMMA_IF_PARENS X() produce una virgola se X è vuoto o inizia con (...) e si espande in altro modo a una sequenza di token che non contiene virgole aggiuntive (di primo livello). (In ogni caso, la sequenza di token contiene anche tutta la virgola di livello superiore da X stessa.)

possiamo usare quel trucco simili:

#define CHOOSE(...) \ 
    LOG(__VA_ARGS__ \ 
     COMMA_IF_PARENS __VA_ARGS__ \ 
     COMMA_IF_PARENS __VA_ARGS__(), \ 
     CHOICES) 

noti che:

  • COMMA_IF_PARENS __VA_ARGS__ produce il numero di virgole in __VA_ARGS__ più 1 se __VA_ARGS__ inizia con (...).
  • COMMA_IF_PARENS __VA_ARGS__() produce il numero di virgole in __VA_ARGS__ più 1 se __VA_ARGS__ è vuoto o inizia con (...). (Si noti che questo può fallire se __VA_ARGS__ termina con il nome di una macro simile a una funzione e non affrontiamo questo potenziale problema qui.)

Let c tramite il numero di virgole in __VA_ARGS__, p essere 1 se __VA_ARGS__ inizia con (...) e 0 altrimenti, e e tramite 1 se __VA_ARGS__ è vuota e 0 altrimenti.

Il numero di argomenti macro prodotti prima CHOICES è 3 c + 2 p + e. Preso modulo 3, il numero di virgole è 0 o 2 per un argomento normale, e 1 se abbiamo una lista vuota di argomenti.

Questo ci dà 6 casi ci stanno a cuore:

#define CHOICES LOG2, impossible, LOG2, LOG1, LOG0, LOG1 
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg 

Tuttavia, questo non funziona del tutto, perché abbiamo bisogno di ritardare l'espansione del LOG(...) macro invocazione fino a dopo espandiamo il macchinario COMMA_IF_PARENS. Un modo per farlo è:

#define LPAREN (
#define EXPAND(...) __VA_ARGS__ 
#define CHOOSE(...) EXPAND(LOG LPAREN COMMA_IF_PARENS [...])) 

Dobbiamo anche aggiungere un'altra virgola alla fine CHOICES modo da avere sempre un argomento (eventualmente vuoto) corrispondente al parametro di ...LOG.

Mettere tutto insieme, otteniamo questo:

#define COMMA_IF_PARENS(...) , 
#define LPAREN (
#define EXPAND(...) __VA_ARGS__ 
#define CHOOSE(...) \ 
    EXPAND(LOG LPAREN \ 
     __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__(), \ 
     LOG2, impossible, LOG2, LOG1, LOG0, LOG1,)) 
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg 

con tutto il resto invariato dal codice. (Questo può essere generalizzato molto di più, ma quanto sopra è sufficiente per dimostrare la tecnica.)

Problemi correlati