Standardalternative zum ## __ VA_ARGS__ Trick von GCC?

149

In C99 gibt es ein bekanntes Problem mit leeren Argumenten für verschiedene Makros.

Beispiel:

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

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

Die Verwendung von BAR()oben ist gemäß dem C99-Standard in der Tat falsch, da es erweitert wird auf:

printf("this breaks!",);

Beachten Sie das nachfolgende Komma - nicht funktionsfähig.

Einige Compiler (z. B. Visual Studio 2010) werden dieses nachfolgende Komma für Sie stillschweigend entfernen. Andere Compiler (zB: GCC) unterstützen das Setzen ##vor __VA_ARGS__, wie folgt:

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

Aber gibt es einen standardkonformen Weg, um dieses Verhalten zu erreichen? Vielleicht mehrere Makros verwenden?

Im Moment ##scheint die Version ziemlich gut unterstützt zu sein (zumindest auf meinen Plattformen), aber ich würde wirklich lieber eine standardkonforme Lösung verwenden.

Präventiv: Ich weiß, ich könnte nur eine kleine Funktion schreiben. Ich versuche dies mit Makros zu tun.

Bearbeiten : Hier ist ein Beispiel (wenn auch einfach), warum ich BAR () verwenden möchte:

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

Dadurch wird meinen BAR () - Protokollierungsanweisungen automatisch eine neue Zeile hinzugefügt, vorausgesetzt, es fmthandelt sich immer um eine C-Zeichenfolge in doppelten Anführungszeichen. Die neue Zeile wird NICHT als separates printf () gedruckt. Dies ist vorteilhaft, wenn die Protokollierung zeilengepuffert ist und asynchron aus mehreren Quellen stammt.

jwd
quelle
3
Warum BARstatt überhaupt verwenden FOO?
GManNickG
@ GMan: Ich habe am Ende ein Beispiel hinzugefügt
jwd
5
@ GMan: Lesen Sie den letzten Satz (:
JWD
7
Diese Funktion wurde für die Aufnahme in C2x vorgeschlagen.
Leushenko
2
@zwol Die neueste Version, die an WG14 gesendet wurde, sieht folgendermaßen aus. Dabei wird eine neue Syntax verwendet, die auf dem __VA_OPT__Schlüsselwort basiert . Dies wurde bereits von C ++ "übernommen" , daher gehe ich davon aus, dass C diesem Beispiel folgen wird. (
Ich

Antworten:

66

Es ist möglich, die Verwendung der GCC- ,##__VA_ARGS__Erweiterung zu vermeiden, wenn Sie bereit sind, eine fest codierte Obergrenze für die Anzahl der Argumente zu akzeptieren, die Sie an Ihr variadisches Makro übergeben können, wie in Richard Hansens Antwort auf diese Frage beschrieben . Wenn Sie jedoch keine solche Begrenzung wünschen, ist es meines Wissens nicht möglich, nur C99-spezifizierte Präprozessorfunktionen zu verwenden. Sie müssen eine Erweiterung der Sprache verwenden. clang und icc haben diese GCC-Erweiterung übernommen, MSVC jedoch nicht.

Bereits im Jahr 2001 habe ich die GCC-Erweiterung für die Standardisierung (und die zugehörige Erweiterung, mit der Sie einen anderen Namen als __VA_ARGS__für den Rest-Parameter verwenden können) in Dokument N976 geschrieben , die jedoch keinerlei Antwort vom Ausschuss erhalten hat. Ich weiß nicht einmal, ob jemand es gelesen hat. 2016 wurde es erneut in vorgeschlagen N2023 , und ich ermutige jeden, der weiß, wie dieser Vorschlag uns in den Kommentaren informieren wird.

zwol
quelle
2
Gemessen an meiner Behinderung, eine Lösung im Internet zu finden, und dem Mangel an Antworten hier, denke ich, dass Sie Recht haben):
JWD
2
Ist n976 das, worauf Sie sich beziehen? Ich suchte den Rest der C Arbeitsgruppe ‚s Dokumente auf eine Antwort , aber nie eine gefunden. Es war nicht einmal auf der Tagesordnung für das anschließende Treffen . Der einzige andere Treffer zu diesem Thema war Norwegens Kommentar Nr. 4 in n868 vor der Ratifizierung von C99 (erneut ohne anschließende Diskussion).
Richard Hansen
4
Ja, speziell die zweite Hälfte davon. Möglicherweise wurde darüber diskutiert, comp.std.caber ich konnte gerade keine in Google Groups finden. Das eigentliche Komitee hat es sicherlich nie beachtet (oder wenn doch, hat mir niemand davon erzählt).
zwol
1
Ich fürchte, ich habe keinen Beweis mehr und bin auch nicht mehr die richtige Person, um mir einen auszudenken. Ich habe die Hälfte von GCCs Präprozessor geschrieben, aber das war vor mehr als zehn Jahren, und ich hätte selbst dann nie an den Trick mit der Argumentzählung gedacht.
zwol
6
Diese Erweiterung funktioniert mit Clang & Intel ICC-Compilern sowie mit gcc.
ACyclic
112

Es gibt einen Argumentzähltrick, den Sie verwenden können.

Hier ist eine standardkonforme Möglichkeit, das zweite BAR()Beispiel in der Frage von jwd zu implementieren :

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

Der gleiche Trick wird verwendet, um:

Erläuterung

Die Strategie besteht darin, __VA_ARGS__das erste Argument und den Rest (falls vorhanden) zu trennen . Dies ermöglicht es, Dinge nach dem ersten Argument, aber vor dem zweiten (falls vorhanden) einzufügen.

FIRST()

Dieses Makro erweitert sich einfach zum ersten Argument und verwirft den Rest.

Die Implementierung ist unkompliziert. Das throwawayArgument stellt sicher, dass FIRST_HELPER()zwei Argumente erhalten werden, was erforderlich ist, weil die... mindestens eines benötigt wird. Mit einem Argument wird es wie folgt erweitert:

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

Mit zwei oder mehr wird es wie folgt erweitert:

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

REST()

Dieses Makro wird auf alles außer dem ersten Argument erweitert (einschließlich des Kommas nach dem ersten Argument, wenn mehr als ein Argument vorhanden ist).

Die Implementierung dieses Makros ist weitaus komplizierter. Die allgemeine Strategie besteht darin, die Anzahl der Argumente (eines oder mehrere) zu zählen und dann entweder REST_HELPER_ONE()(wenn nur ein Argument angegeben wird) oder REST_HELPER_TWOORMORE()(wenn zwei oder mehr Argumente angegeben sind) zu erweitern. REST_HELPER_ONE()erweitert sich einfach zu nichts - es gibt keine Argumente nach dem ersten, so dass die verbleibenden Argumente die leere Menge sind. REST_HELPER_TWOORMORE()ist auch unkompliziert - es wird zu einem Komma erweitert, gefolgt von allem außer dem ersten Argument.

Die Argumente werden mit dem NUM()Makro gezählt. Dieses Makro wird erweitert, ONEwenn nur ein Argument angegeben wird, TWOORMOREwenn zwischen zwei und neun Argumente angegeben sind, und wird unterbrochen, wenn 10 oder mehr Argumente angegeben werden (da es auf das 10. Argument erweitert wird).

Das NUM()Makro verwendet das SELECT_10TH()Makro, um die Anzahl der Argumente zu bestimmen. Wie der Name schon sagt, wird SELECT_10TH()einfach auf das 10. Argument erweitert. Aufgrund der Auslassungspunkte SELECT_10TH()müssen mindestens 11 Argumente übergeben werden (der Standard besagt, dass mindestens ein Argument für die Auslassungspunkte vorhanden sein muss). DeshalbNUM() geht throwawayals letztes Argument (ohne sie zu einem Argument übergeben NUM()würde in nur 10 Argumente übergeben werden SELECT_10TH(), die den Standard verletzen würde).

Die Auswahl von entweder REST_HELPER_ONE()oder REST_HELPER_TWOORMORE()erfolgt durch Verketten REST_HELPER_mit der Erweiterung von NUM(__VA_ARGS__)in REST_HELPER2(). Beachten Sie, dass der Zweck vonREST_HELPER() besteht, sicherzustellen, dass NUM(__VA_ARGS__)es vollständig erweitert ist, bevor es verkettet wird REST_HELPER_.

Die Erweiterung mit einem Argument lautet wie folgt:

  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. (leer)

Die Erweiterung mit zwei oder mehr Argumenten erfolgt wie folgt:

  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
Richard Hansen
quelle
1
Beachten Sie, dass dies fehlschlägt, wenn Sie BAR mit 10 oder mehr Argumenten aufrufen. Obwohl es relativ einfach ist, es auf mehrere Argumente auszudehnen, hat es immer eine Obergrenze für die Anzahl der Argumente, mit denen es umgehen kann
Chris Dodd
2
@ ChrisDodd: Richtig. Leider scheint es keine Möglichkeit zu geben, eine Begrenzung der Anzahl der Argumente zu vermeiden, ohne sich auf compilerspezifische Erweiterungen zu verlassen. Außerdem ist mir keine Möglichkeit bekannt, zuverlässig zu testen, ob zu viele Argumente vorhanden sind (damit eine nützliche Compiler-Fehlermeldung anstelle eines seltsamen Fehlers gedruckt werden kann).
Richard Hansen
17

Keine allgemeine Lösung, aber im Fall von printf können Sie eine neue Zeile anhängen wie:

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

Ich glaube, es ignoriert alle zusätzlichen Argumente, auf die in der Formatzeichenfolge nicht verwiesen wird. Sie könnten also wahrscheinlich sogar davonkommen mit:

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

Ich kann nicht glauben, dass C99 ohne eine Standardmethode genehmigt wurde. AFAICT das Problem besteht auch in C ++ 11.

Marsh Ray
quelle
Das Problem mit dieser zusätzlichen 0 ist, dass sie tatsächlich im Code landet, wenn sie die vararg-Funktion aufruft. Suchen Sie nach einer Lösung von Richard Hansen
Pavel P
@Pavel ist in Bezug auf das zweite Beispiel korrekt, aber das erste funktioniert hervorragend. +1.
Kirbyfan64sos
11

Es gibt eine Möglichkeit, diesen speziellen Fall mit Boost.Preprocessor zu behandeln . Mit BOOST_PP_VARIADIC_SIZE können Sie die Größe der Argumentliste überprüfen und dann bedingt auf ein anderes Makro erweitern. Das einzige Manko dabei ist, dass es nicht zwischen 0 und 1 Argument unterscheiden kann, und der Grund dafür wird klar, wenn Sie Folgendes berücksichtigen:

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

Die leere Makroargumentliste besteht tatsächlich aus einem Argument, das zufällig leer ist.

In diesem Fall haben wir Glück, da Ihr gewünschtes Makro immer mindestens 1 Argument hat. Wir können es als zwei "Überladungs" -Makros implementieren:

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

Und dann noch ein Makro, um zwischen ihnen zu wechseln, wie zum Beispiel:

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

oder

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

Was auch immer Sie besser lesbar finden (ich bevorzuge das erste, da es Ihnen eine allgemeine Form zum Überladen von Makros für die Anzahl der Argumente gibt).

Es ist auch möglich, dies mit einem einzelnen Makro zu tun, indem auf die Liste der variablen Argumente zugegriffen und diese mutiert wird. Sie ist jedoch weitaus weniger lesbar und sehr spezifisch für dieses Problem:

#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__)))) \
    /**/

Warum gibt es auch kein BOOST_PP_ARRAY_ENUM_TRAILING? Es würde diese Lösung viel weniger schrecklich machen.

Bearbeiten: Okay, hier ist ein BOOST_PP_ARRAY_ENUM_TRAILING und eine Version, die es verwendet (dies ist jetzt meine Lieblingslösung):

#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__)))) \
    /**/
DRayX
quelle
1
Schön, etwas über Boost.Preprocessor zu lernen, +1. Beachten Sie, dass BOOST_PP_VARIADIC_SIZE()derselbe Argumentzähltrick verwendet wird, den ich in meiner Antwort dokumentiert habe, und dass er dieselbe Einschränkung aufweist (er wird unterbrochen, wenn Sie mehr als eine bestimmte Anzahl von Argumenten übergeben).
Richard Hansen
1
Ja, ich habe gesehen, dass Ihr Ansatz der gleiche war, der von Boost verwendet wurde, aber die Boost-Lösung ist sehr gut gewartet und verfügt über viele andere wirklich nützliche Funktionen für die Entwicklung komplexerer Makros. Das Rekursionsmaterial ist besonders cool (und wird hinter den Kulissen des letzten Ansatzes verwendet, der BOOST_PP_ARRAY_ENUM verwendet).
DRayX
1
Eine Boost-Antwort, die tatsächlich für das c- Tag gilt! Hurra!
Justin
6

Ein sehr einfaches Makro, das ich für den Debug-Druck verwende:

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

Unabhängig davon, wie viele Argumente an DBG übergeben werden, gibt es keine c99-Warnung.

Der Trick ist __DBG_INT , einen Dummy-Parameter hinzuzufügen, sodass ...immer mindestens ein Argument vorhanden ist und c99 erfüllt ist.

SimonW
quelle
5

Ich bin kürzlich auf ein ähnliches Problem gestoßen, und ich glaube, es gibt eine Lösung.

Die Schlüsselidee ist, dass es eine Möglichkeit gibt, ein Makro zu schreiben, NUM_ARGSum die Anzahl der Argumente zu zählen, die einem variadischen Makro gegeben werden. Sie können eine Variation von NUM_ARGSzum Erstellen verwenden NUM_ARGS_CEILING2, mit der Sie feststellen können, ob einem variadischen Makro 1 Argument oder 2 oder mehr Argumente zugewiesen wurden. Dann können Sie Ihr BarMakro so schreiben , dass es NUM_ARGS_CEILING2und verwendetCONCAT seine Argumente an eines von zwei Hilfsmakros sendet: eines, das genau 1 Argument erwartet, und eines, das eine variable Anzahl von Argumenten größer als 1 erwartet.

Hier ist ein Beispiel, in dem ich diesen Trick verwende, um das Makro zu schreiben UNIMPLEMENTED, das sehr ähnlich ist BAR:

SCHRITT 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

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

Schritt 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__)

SCHRITT 3:

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

Wo CONCAT wie gewohnt implementiert wird. Als kurzer Hinweis, wenn das oben Genannte verwirrend erscheint: Das Ziel von CONCAT besteht darin, auf einen anderen Makro- "Aufruf" zu erweitern.

Beachten Sie, dass NUM_ARGS selbst nicht verwendet wird. Ich habe es nur eingefügt, um den grundlegenden Trick hier zu veranschaulichen. Siehe Jens Gustedts P99-Blog für eine schöne Behandlung.

Zwei Anmerkungen:

  • NUM_ARGS ist in der Anzahl der behandelten Argumente begrenzt. Meins kann nur bis zu 20 verarbeiten, obwohl die Anzahl völlig willkürlich ist.

  • Wie gezeigt, hat NUM_ARGS die Gefahr, dass es 1 zurückgibt, wenn 0 Argumente angegeben werden. Der Kern davon ist, dass NUM_ARGS technisch [Kommas + 1] zählt und keine Argumente. In diesem speziellen Fall funktioniert es tatsächlich zu unserem Vorteil. _UNIMPLEMENTED1 verarbeitet ein leeres Token einwandfrei und erspart uns das Schreiben von _UNIMPLEMENTED0. Gustedt hat auch dafür eine Problemumgehung, obwohl ich sie nicht verwendet habe und nicht sicher bin, ob sie für das, was wir hier tun, funktionieren würde.

User123abc
quelle
+1 für das Aufrufen des Argumentzähltricks, -1 für das wirklich schwere Befolgen
Richard Hansen
Die Kommentare, die Sie hinzugefügt haben, waren eine Verbesserung, aber es gibt immer noch eine Reihe von Problemen: 1. Sie diskutieren und definieren NUM_ARGS, verwenden sie jedoch nicht. 2. Was ist der Zweck von UNIMPLEMENTED? 3. Sie lösen niemals das Beispielproblem in der Frage. 4. Wenn Sie Schritt für Schritt durch die Erweiterung gehen, wird die Funktionsweise veranschaulicht und die Rolle jedes Hilfsmakros erläutert. 5. Das Diskutieren von 0 Argumenten lenkt ab. Das OP fragte nach der Einhaltung von Standards, und 0 Argumente sind verboten (C99 6.10.3p4). 6. Schritt 1.5? Warum nicht Schritt 2? 7. "Schritte" impliziert Aktionen, die nacheinander ausgeführt werden. Das ist nur Code.
Richard Hansen
8. Sie verlinken auf den gesamten Blog, nicht auf den entsprechenden Beitrag. Ich konnte den Beitrag, auf den Sie sich bezogen, nicht finden. 9. Der letzte Absatz ist umständlich: Diese Methode ist dunkel; Deshalb hatte noch niemand zuvor eine korrekte Lösung veröffentlicht. Wenn es funktioniert und dem Standard entspricht, muss Zacks Antwort falsch sein. 10. Sie sollten definieren CONCAT()- nehmen Sie nicht an, dass die Leser wissen, wie es funktioniert.
Richard Hansen
(Bitte interpretieren Sie dieses Feedback nicht als Angriff. Ich wollte Ihre Antwort wirklich positiv bewerten, fühlte mich aber nicht wohl dabei, es sei denn, es wurde verständlicher. Wenn Sie die Klarheit Ihrer Antwort verbessern können, werde ich es tun upvote deine und lösche meine.)
Richard Hansen
2
Ich hätte nie an diesen Ansatz gedacht und ungefähr die Hälfte des aktuellen Präprozessors von GCC geschrieben! Trotzdem sage ich immer noch, dass "es keinen Standardweg gibt, um diesen Effekt zu erzielen", da sowohl Ihre als auch Richards Techniken eine Obergrenze für die Anzahl der Argumente für das Makro festlegen.
zwol
2

Dies ist die vereinfachte Version, die ich verwende. Es basiert auf den großartigen Techniken der anderen Antworten hier, so viele Requisiten an sie:

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

Das ist es.

Wie bei anderen Lösungen ist dies auf die Anzahl der Argumente des Makros beschränkt. Um mehr zu unterstützen, fügen Sie mehr Parameter _SELECTund mehr NArgumente hinzu. Die Argumentnamen zählen nach unten (statt nach oben), um daran zu erinnern, dass die Zählung basiertSUFFIX Argument in umgekehrter Reihenfolge bereitgestellt wird.

Diese Lösung behandelt 0 Argumente so, als wäre es 1 Argument. Also BAR()nominell "funktioniert", weil es sich ausdehnt _SELECT(_BAR,,N,N,N,N,1)(), sich ausdehnt _BAR_1()(), sich ausdehnt, sich ausdehntprintf("\n") .

Wenn Sie möchten, können Sie kreativ werden, _SELECTindem Sie verschiedene Makros für eine unterschiedliche Anzahl von Argumenten verwenden und bereitstellen. Hier haben wir zum Beispiel ein LOG-Makro, das vor dem Format ein 'level'-Argument verwendet. Wenn das Format fehlt, wird "(keine Nachricht)" protokolliert. Wenn nur ein Argument vorhanden ist, wird es über "% s" protokolliert. Andernfalls wird das Formatargument als printf-Formatzeichenfolge für die verbleibenden Argumente behandelt.

#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
*/
"Neurobur"
quelle
Dies löst beim Kompilieren mit -pedantic immer noch eine Warnung aus.
PSkocik
0

In Ihrer Situation (mindestens 1 Argument vorhanden, nie 0), können Sie festlegen , BARwie BAR(...), verwenden Jens Gustedt ist HAS_COMMA(...) ein Komma zu erkennen und dann zu versenden BAR0(Fmt)oderBAR1(Fmt,...) entsprechend.

Dies:

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

Kompiliert mit -pedanticohne Vorwarnung.

PSkocik
quelle
0

C (gcc) , 762 Bytes

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

Probieren Sie es online aus!

Geht davon aus:

  • Kein Argument enthält Komma oder Klammern
  • Kein Argument enthält A~ G(kann in hard_collide umbenannt werden)
14 m2
quelle
Die no arg contain commaEinschränkung kann umgangen werden, indem nach einigen weiteren Durchgängen Multi überprüft wird, aber no bracketimmer noch vorhanden ist
l4m2
-2

Die Standardlösung ist die Verwendung von FOOanstelle von BAR. Es gibt ein paar seltsame Fälle von Argumentumordnung, die es wahrscheinlich nicht für Sie tun kann (obwohl ich wette, dass jemand clevere Hacks entwickeln kann, um sie __VA_ARGS__basierend auf der Anzahl der darin enthaltenen Argumente unter bestimmten Bedingungen zu zerlegen und wieder zusammenzusetzen !), Aber im Allgemeinen mit FOO"normalerweise". funktioniert einfach.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
1
Die Frage war: "Gibt es einen standardkonformen Weg, um dieses Verhalten zu erreichen?"
Marsh Ray
2
Und die Frage enthält eine Begründung dafür, dass FOO seit Ewigkeiten nicht mehr verwendet wird.
Pavel Šimerda