Alternativa standard al trucco ## __ VA_ARGS__ di GCC?


151

Esiste un problema ben noto con argomenti vuoti per le macro variadiche in C99.

esempio:

#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

L'uso di cui BAR()sopra è in effetti errato secondo lo standard C99, poiché si espanderà a:

printf("this breaks!",);

Nota la virgola finale - non praticabile.

Alcuni compilatori (ad es. Visual Studio 2010) elimineranno tranquillamente quella virgola finale per te. Altri compilatori (ad es. GCC) supportano la messa ##in primo piano __VA_ARGS__, in questo modo:

#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

Ma esiste un modo conforme agli standard per ottenere questo comportamento? Forse usando più macro?

In questo momento, la ##versione sembra abbastanza ben supportata (almeno sulle mie piattaforme), ma preferirei davvero utilizzare una soluzione conforme agli standard.

Prevenzione: so che potrei semplicemente scrivere una piccola funzione. Sto provando a farlo usando le macro.

Modifica : ecco un esempio (anche se semplice) del motivo per cui vorrei usare BAR ():

#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

Ciò aggiunge automaticamente una nuova riga alle mie istruzioni di registrazione BAR (), supponendo che fmtsia sempre una stringa C tra virgolette doppie. NON stampa la nuova riga come printf () separato, il che è vantaggioso se la registrazione è bufferizzata in linea e proviene da più origini in modo asincrono.


3
Perché usare BARinvece che FOOin primo luogo?
GManNickG

@GMan: ho aggiunto un esempio alla fine
jwd,

5
@GMan: leggi l'ultima frase (:
jwd,

7
Questa funzione è stata proposta per l'inclusione in C2x.
Leushenko,

2
@zwol l'ultima versione presentata alla WG14 aspetto come questo , che utilizza una nuova sintassi in base alla __VA_OPT__parola chiave. Questo è già stato "adottato" da C ++, quindi mi aspetto che C segua l'esempio. (non so se ciò significhi che è stato accelerato in C ++ 17 o se è impostato per C ++ 20)
Leushenko

Risposte:


66

È possibile evitare l'uso ,##__VA_ARGS__dell'estensione di GCC se si è disposti ad accettare un limite superiore codificato sul numero di argomenti che è possibile passare alla macro variadica, come descritto nella risposta di Richard Hansen a questa domanda . Se non si desidera avere tale limite, tuttavia, per quanto ne so, non è possibile utilizzare solo le funzionalità di preprocessore specificate da C99; devi usare qualche estensione per la lingua. clang e icc hanno adottato questa estensione GCC, ma MSVC no.

Nel 2001 ho scritto l'estensione GCC per la standardizzazione (e la relativa estensione che consente di utilizzare un nome diverso da quello __VA_ARGS__per il parametro rest) nel documento N976 , ma che non ha ricevuto alcuna risposta dal comitato; Non so nemmeno se qualcuno l'ha letto. Nel 2016 è stato riproposto in N2023 e incoraggio chiunque sappia come ci farà sapere la proposta nei commenti.


2
A giudicare dalla mia disabilità nel trovare una soluzione sul web e la mancanza di risposte qui, credo che tu abbia ragione):
jwd

2
N976 è ciò a cui ti riferisci? Ho cercato il resto del gruppo C di lavoro 's documenti per una risposta, ma mai trovato uno. Non era nemmeno all'ordine del giorno della riunione successiva . L'unico altro successo su questo argomento è stato il commento n. 4 della Norvegia nell'868 di prima della ratifica del C99 (di nuovo senza discussione di follow-up).
Richard Hansen,

4
Sì, in particolare la seconda metà. Potrebbero esserci state discussioni comp.std.cma non sono riuscito a trovarne nessuno in Google Gruppi in questo momento; di certo non ha mai ricevuto alcuna attenzione dal comitato effettivo (o, in caso affermativo, nessuno me lo ha mai detto).
zwol,

1
Temo di non avere una prova, né sono più la persona giusta per provare a pensarne una. Ho scritto la metà del preprocessore di GCC, ma è stato più di dieci anni fa, e non avrei mai pensato al trucco di conteggio delle discussioni qui sotto, nemmeno allora.
zwol,

6
Questa estensione funziona con i compilatori clang & intel icc e con gcc.
ACyclic

112

C'è un trucco per il conteggio degli argomenti che puoi usare.

Ecco un modo conforme allo standard per implementare il secondo BAR()esempio nella domanda di jwd:

#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

Questo stesso trucco è usato per:

Spiegazione

La strategia è quella di separare __VA_ARGS__il primo argomento e il resto (se presente). Questo rende possibile inserire elementi dopo il primo argomento ma prima del secondo (se presente).

FIRST()

Questa macro si espande semplicemente al primo argomento, scartando il resto.

L'implementazione è semplice. L' throwawayargomento garantisce che FIRST_HELPER()ottiene due argomenti, che è necessario perché ne ...necessita almeno uno. Con un argomento, si espande come segue:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

Con due o più, si espande come segue:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

Questa macro si espande a tutto tranne che al primo argomento (inclusa la virgola dopo il primo argomento, se è presente più di un argomento).

L'implementazione di questa macro è molto più complicata. La strategia generale consiste nel contare il numero di argomenti (uno o più di uno) e quindi espandere in REST_HELPER_ONE()(se viene fornito solo un argomento) o REST_HELPER_TWOORMORE()(se vengono forniti due o più argomenti). REST_HELPER_ONE()si espande semplicemente in nulla - non ci sono argomenti dopo il primo, quindi gli argomenti rimanenti sono l'insieme vuoto. REST_HELPER_TWOORMORE()è anche semplice: si espande in una virgola seguita da tutto tranne il primo argomento.

Gli argomenti vengono contati utilizzando la NUM()macro. Questa macro si espande ONEse viene fornito un solo argomento, TWOORMOREse vengono forniti tra due e nove argomenti, e si interrompe se vengono forniti 10 o più argomenti (perché si espande al decimo argomento).

La NUM()macro utilizza la SELECT_10TH()macro per determinare il numero di argomenti. Come suggerisce il nome, SELECT_10TH()si espande semplicemente al suo decimo argomento. A causa dei puntini di sospensione, SELECT_10TH()devono essere passati almeno 11 argomenti (lo standard dice che deve esserci almeno un argomento per i puntini di sospensione). Questo è il motivo per cui NUM()passa throwawaycome ultimo argomento (senza di esso, passare un argomento a NUM()comporterebbe il passaggio a soli 10 argomenti SELECT_10TH(), il che violerebbe lo standard).

La selezione di uno REST_HELPER_ONE()o REST_HELPER_TWOORMORE()viene effettuata concatenando REST_HELPER_con l'espansione di NUM(__VA_ARGS__)in REST_HELPER2(). Si noti che lo scopo di REST_HELPER()è assicurarsi che NUM(__VA_ARGS__)sia completamente espanso prima di essere concatenato REST_HELPER_.

L'espansione con un argomento è la seguente:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (vuoto)

L'espansione con due o più argomenti è la seguente:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

1
Nota che questo fallirà se chiami BAR con 10 o più argomenti, e sebbene sia relativamente facile estenderlo a più argomenti, avrà sempre un limite superiore al numero di argomenti che può trattare
Chris Dodd,

2
@ChrisDodd: Corretto. Sfortunatamente, non sembra esserci un modo per evitare un limite nel numero di argomenti senza fare affidamento su estensioni specifiche del compilatore. Inoltre, non sono a conoscenza di un modo per testare in modo affidabile se ci sono troppi argomenti (in modo che un utile messaggio di errore del compilatore possa essere stampato, piuttosto che uno strano errore).
Richard Hansen,

17

Non una soluzione generale, ma nel caso di printf potresti aggiungere una nuova riga come:

#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

Credo che ignori qualsiasi argomento aggiuntivo a cui non si fa riferimento nella stringa di formato. Quindi probabilmente potresti anche cavartela con:

#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

Non riesco a credere che C99 sia stato approvato senza un modo standard per farlo. AFAICT il problema esiste anche in C ++ 11.


il problema con questo extra 0 è che finirà effettivamente nel codice se chiama la funzione vararg. Verifica la soluzione fornita da Richard Hansen
Pavel P

@Pavel ha ragione sul secondo esempio, ma il primo funziona alla grande. +1.
Kirbyfan64sos,

11

C'è un modo per gestire questo caso specifico usando qualcosa come Boost.Preprocessor . È possibile utilizzare BOOST_PP_VARIADIC_SIZE per verificare la dimensione dell'elenco degli argomenti, quindi espanderlo in modo condizionale in un'altra macro. L'unico difetto di questo è che non è in grado di distinguere tra 0 e 1 argomento e il motivo diventa chiaro una volta considerato quanto segue:

BOOST_PP_VARIADIC_SIZE()      // expands to 1
BOOST_PP_VARIADIC_SIZE(,)     // expands to 2
BOOST_PP_VARIADIC_SIZE(,,)    // expands to 3
BOOST_PP_VARIADIC_SIZE(a)     // expands to 1
BOOST_PP_VARIADIC_SIZE(a,)    // expands to 2
BOOST_PP_VARIADIC_SIZE(,b)    // expands to 2
BOOST_PP_VARIADIC_SIZE(a,b)   // expands to 2
BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

L'elenco di argomenti macro vuoto è in realtà costituito da un argomento che risulta essere vuoto.

In questo caso, siamo fortunati poiché la macro desiderata ha sempre almeno 1 argomento, possiamo implementarla come due macro di "sovraccarico":

#define BAR_0(fmt) printf(fmt "\n")
#define BAR_1(fmt, ...) printf(fmt "\n", __VA_ARGS__)

E poi un'altra macro per passare da una all'altra, come ad esempio:

#define BAR(...) \
    BOOST_PP_CAT(BAR_, BOOST_PP_GREATER(
        BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) \
    /**/

o

#define BAR(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        BAR_1, BAR_0)(__VA_ARGS__) \
    /**/

Qualunque cosa tu trovi più leggibile (preferisco la prima in quanto ti dà una forma generale per sovraccaricare le macro sul numero di argomenti).

È anche possibile farlo con una singola macro accedendo e mutando l'elenco degli argomenti variabili, ma è molto meno leggibile ed è molto specifico per questo problema:

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_COMMA_IF( \
        BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) \
    BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

Inoltre, perché non esiste BOOST_PP_ARRAY_ENUM_TRAILING? Renderebbe questa soluzione molto meno orribile.

Modifica: Bene, ecco un BOOST_PP_ARRAY_ENUM_TRAILING e una versione che lo utilizza (questa è ora la mia soluzione preferita):

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) \
    BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) \
    /**/

#define BAR(...) printf( \
    BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "\n" \
    BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( \
        BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) \
    /**/

1
Bello conoscere Boost.Preprocessor, +1. Nota che BOOST_PP_VARIADIC_SIZE()usa lo stesso trucco di conteggio degli argomenti che ho documentato nella mia risposta e ha la stessa limitazione (si romperà se passi più di un certo numero di argomenti).
Richard Hansen,

1
Sì, ho visto che il tuo approccio era lo stesso utilizzato da Boost, ma la soluzione boost è molto ben mantenuta e ha molte altre funzioni davvero utili da utilizzare quando si sviluppano macro più sofisticate. Il materiale ricorsivo è particolarmente interessante (e utilizzato dietro le quinte nell'ultimo approccio che utilizza BOOST_PP_ARRAY_ENUM).
DRayX,

1
Una risposta Boost che si applica effettivamente al tag c ! Evviva!
Giustino,

6

Una macro molto semplice che sto usando per la stampa di debug:

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "nice!");
        return 0;
}

Indipendentemente dal numero di argomenti passati a DBG, non esiste alcun avviso c99.

Il trucco sta __DBG_INTaggiungendo un parametro fittizio, quindi ...avrà sempre almeno un argomento e c99 è soddisfatto.


5

Di recente ho riscontrato un problema simile e credo che ci sia una soluzione.

L'idea chiave è che esiste un modo per scrivere una macro NUM_ARGSper contare il numero di argomenti forniti da una macro variadica. È possibile utilizzare una variante di NUM_ARGSbuild NUM_ARGS_CEILING2, che può indicare se a una macro variadica viene assegnato 1 argomento o 2 o più argomenti. Quindi è possibile scrivere la Barmacro in modo che utilizzi NUM_ARGS_CEILING2e CONCATinviare i suoi argomenti a una delle due macro di supporto: una che prevede esattamente 1 argomento e un'altra che prevede un numero variabile di argomenti maggiore di 1.

Ecco un esempio in cui utilizzo questo trucco per scrivere la macro UNIMPLEMENTED, che è molto simile a BAR:

PASSO 1:

/** 
 * A variadic macro which counts the number of arguments which it is
 * passed. Or, more precisely, it counts the number of commas which it is
 * passed, plus one.
 *
 * Danger: It can't count higher than 20. If it's given 0 arguments, then it
 * will evaluate to 1, rather than to 0.
 */

#define NUM_ARGS(...)                                                   \
    NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13,       \
                     12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)    

#define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7,        \
                         a8, a9, a10, a11, a12, a13,        \
                         a14, a15, a16, a17, a18, a19, a20, \
                         N, ...)                            \
    N

PASSO 1.5:

/*
 * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or
 * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if
 * it's given more than 20 args.
 */

#define NUM_ARGS_CEIL2(...)                                           \
    NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
                     2, 2, 2, 2, 2, 2, 2, 1)

Passo 2:

#define _UNIMPLEMENTED1(msg)                                        \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__)

#define _UNIMPLEMENTED2(msg, ...)                                   \
    log("My creator has forsaken me. %s:%s:%d." msg, __FILE__,      \
        __func__, __LINE__, __VA_ARGS__)

PASSAGGIO 3:

#define UNIMPLEMENTED(...)                                              \
    CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

Dove CONCAT è implementato nel solito modo. Come suggerimento rapido, se quanto sopra sembra confuso: l'obiettivo di CONCAT è quello di espandersi in un'altra macro "chiamata".

Tieni presente che NUM_ARGS non viene utilizzato. L'ho appena incluso per illustrare il trucco di base qui. Vedi il blog P99 di Jens Gustedt per un bel trattamento.

Due note:

  • NUM_ARGS è limitato nel numero di argomenti che gestisce. Il mio può gestire solo fino a 20, anche se il numero è totalmente arbitrario.

  • NUM_ARGS, come mostrato, ha un errore in quanto restituisce 1 quando vengono dati 0 argomenti. L'essenza è che NUM_ARGS sta contando tecnicamente [virgole + 1], e non args. In questo caso particolare, funziona effettivamente a nostro vantaggio. _UNIMPLEMENTED1 gestirà bene un token vuoto e ci evita di dover scrivere _UNIMPLEMENTED0. Gustedt ha anche una soluzione alternativa, anche se non l'ho usato e non sono sicuro che funzionerebbe per quello che stiamo facendo qui.


+1 per aver tirato fuori il trucco del conteggio degli argomenti, -1 per essere davvero difficile da seguire
Richard Hansen,

I commenti che hai aggiunto sono stati un miglioramento, ma ci sono ancora una serie di problemi: 1. Discuti e definisci NUM_ARGSma non lo usi. 2. Qual è lo scopo di UNIMPLEMENTED? 3. Non risolvi mai il problema di esempio nella domanda. 4. Camminare attraverso l'espansione un passo alla volta illustrerebbe come funziona e spiegherebbe il ruolo di ogni macro helper. 5. Discutere 0 argomenti è fonte di distrazione; il PO chiedeva la conformità agli standard e 0 argomenti sono vietati (C99 6.10.3p4). 6. Passaggio 1.5? Perché non passare 2? 7. "Passaggi" implica azioni che si verificano in sequenza; questo è solo codice.
Richard Hansen,

8. Ti colleghi a tutto il blog, non al post pertinente. Non sono riuscito a trovare il post a cui ti riferivi. 9. L'ultimo paragrafo è imbarazzante: questo metodo è oscuro; ecco perché nessun altro aveva pubblicato una soluzione corretta prima. Inoltre, se funziona e aderisce allo standard, la risposta di Zack deve essere errata. 10. Dovresti definire CONCAT()- non dare per scontato che i lettori sappiano come funziona.
Richard Hansen,

(Per favore, non interpretare questo feedback come un attacco - Volevo davvero dare il massimo alla tua risposta ma non mi sentivo a mio agio nel farlo a meno che non fosse reso più facile da capire. Se puoi migliorare la chiarezza della tua risposta, vota il tuo ed elimina il mio.)
Richard Hansen il

2
Non avrei mai pensato a questo approccio e ho scritto circa la metà dell'attuale preprocessore di GCC! Detto questo, continuo a dire che "non esiste un modo standard per ottenere questo effetto" perché sia ​​le tue tecniche che quelle di Richard impongono un limite superiore al numero di argomenti alla macro.
zwol

2

Questa è la versione semplificata che uso. Si basa sulle grandi tecniche delle altre risposte qui, così tanti oggetti di scena:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX

#define _BAR_1(fmt)      printf(fmt "\n")
#define _BAR_N(fmt, ...) printf(fmt "\n", __VA_ARGS__);
#define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
    return 0;
}

Questo è tutto.

Come con altre soluzioni, questo è limitato al numero di argomenti della macro. Per supportare di più, aggiungi più parametri _SELECTe più Nargomenti. I nomi degli argomenti eseguono il conto alla rovescia (anziché verso l'alto) per ricordare che l' SUFFIXargomento basato sul conteggio viene fornito in ordine inverso.

Questa soluzione tratta 0 argomenti come se fosse 1 argomento. Quindi BAR()nominalmente "funziona", perché si espande a _SELECT(_BAR,,N,N,N,N,1)(), che si espande a _BAR_1()(), che si espande a printf("\n").

Se lo desideri, puoi diventare creativo con l'uso di _SELECTe fornire macro diverse per un numero diverso di argomenti. Ad esempio, qui abbiamo una macro LOG che accetta un argomento di "livello" prima del formato. Se manca il formato, registra "(nessun messaggio)", se c'è solo 1 argomento, lo registra attraverso "% s", altrimenti tratterà l'argomento format come una stringa di formato printf per gli argomenti rimanenti.

#define _LOG_1(lvl)          printf("[%s] (no message)\n", #lvl)
#define _LOG_2(lvl,fmt)      printf("[%s] %s\n", #lvl, fmt)
#define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "\n", #lvl, __VA_ARGS__)
#define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__)

int main(int argc, char *argv[]) {
    LOG(INFO);
    LOG(DEBUG, "here is a log message");
    LOG(WARN, "here is a log message with param: %d", 42);
    return 0;
}
/* outputs:
[INFO] (no message)
[DEBUG] here is a log message
[WARN] here is a log message with param: 42
*/

Questo attiva ancora un avviso quando compilato con -pedantic.
PSkocik,

1

Nella tua situazione (almeno 1 argomento presente, mai 0), puoi definire BARcome BAR(...), usare Jens Gustedt HAS_COMMA(...) per rilevare una virgola e quindi inviare BAR0(Fmt)o di BAR1(Fmt,...)conseguenza.

Questo:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

si compila -pedanticsenza preavviso.


0

C (gcc) , 762 byte

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

Provalo online!

assume:

  • Nessun argomento contiene virgola o parentesi
  • Nessun argomento contiene A~ G(può rinominare in hard_collide)

La no arg contain commalimitazione può essere aggirata controllando il multi dopo qualche altro passaggio, ma no bracketancora lì
l4m2

-2

La soluzione standard è usare FOOinvece di BAR. Ci sono alcuni strani casi di riordino di argomenti che probabilmente non possono fare per te (anche se scommetto che qualcuno può inventare hack intelligenti per disassemblare e rimontare in __VA_ARGS__base al numero di argomenti in esso!) Ma in generale usando FOO"solito" funziona e basta.


1
La domanda era "esiste un modo conforme agli standard per ottenere questo comportamento?"
Marsh Ray,

2
E la domanda ha incluso una logica per non usare FOO da anni.
Pavel Šimerda,
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.