wake-up-neo.com

Standardalternative zum GCC-Trick ## __ VA_ARGS__?

Es gibt ein bekanntesProblem mit leeren Argumenten für variadische Makros in C99.

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 in der Tat gemäß dem C99-Standard falsch, da es zu Folgendem erweitert wird:

printf("this breaks!",);

Beachten Sie das nachfolgende Komma - nicht verarbeitbar.

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

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

Aber gibt es eine standardkonforme Methode, um dieses Verhalten zu erreichen? Vielleicht verwenden Sie mehrere Makros?

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

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

Edit: Hier ist ein Beispiel (obwohl 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 ein Zeilenvorschub hinzugefügt, vorausgesetzt, fmt ist immer ein doppelter Anführungsstrich. Die Zeile wird NICHT als separates printf () gedruckt, was vorteilhaft ist, wenn die Protokollierung in Zeilen gepuffert wird und asynchron aus mehreren Quellen stammt.

131
jwd

Es ist möglich, die Verwendung der GCC-Erweiterung ,##__VA_ARGS__ zu vermeiden, wenn Sie bereit sind, eine fest vorgegebene 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 keine solche Begrenzung wünschen, ist es meines Wissens jedoch nicht möglich, nur C99-spezifizierte Präprozessor-Funktionen zu verwenden. Sie müssen eine Erweiterung der Sprache verwenden. clang und icc haben diese GCC-Erweiterung übernommen, MSVC jedoch nicht. 

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 document N976 aufgeschrieben, der jedoch keine Antwort vom Komitee erhielt. Ich weiß nicht einmal, ob jemand es gelesen hat. 2016 wurde es erneut in N2023 vorgeschlagen, und ich ermutige jeden, der weiß, wie dieser Vorschlag uns in den Kommentaren mitteilen wird.

55
zwol

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

Hier ist eine standardkonforme Möglichkeit, das zweite BAR()-Beispiel in jwds Frage 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;
}

Derselbe Trick wird verwendet, um:

Erläuterung

Die Strategie besteht darin, __VA_ARGS__ in das erste Argument und den Rest (falls vorhanden) zu trennen. Dies ermöglicht das Einfügen von Inhalten nach dem ersten Argument, aber vor dem zweiten (falls vorhanden).

FIRST()

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

Die Implementierung ist unkompliziert. Das Argument throwaway stellt sicher, dass FIRST_HELPER() zwei Argumente erhält. Dies ist erforderlich, da ... mindestens eines benötigt. Mit einem Argument wird es wie folgt erweitert:

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

Bei 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 bis auf das erste Argument auf alles erweitert (einschließlich des Kommas nach dem ersten Argument, wenn es mehr als ein Argument gibt).

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

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

Das Makro NUM() verwendet das Makro SELECT_10TH(), um die Anzahl der Argumente zu bestimmen. Wie der Name schon sagt, wird SELECT_10TH() einfach auf das 10. Argument erweitert. Wegen der Ellipse muss SELECT_10TH() mindestens 11 Argumente übergeben werden (der Standard besagt, dass es mindestens ein Argument für die Ellipse geben muss). Aus diesem Grund übergibt NUM()throwaway als letztes Argument (ohne dieses Argument würde die Übergabe eines Arguments an NUM() dazu führen, dass nur 10 Argumente an SELECT_10TH() übergeben werden, was gegen den Standard verstoßen würde).

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

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

Die Erweiterung mit zwei oder mehr Argumenten sieht folgendermaßen aus:

  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
108
Richard Hansen

Keine generelle Lösung, aber im Fall von printf könnten Sie eine neue Zeile wie:

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

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

#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 dafür genehmigt wurde. AFAICT das Problem besteht auch in C++ 11.

15
Marsh Ray

Es gibt eine Möglichkeit, diesen speziellen Fall mit etwas wie Boost.Preprocessor zu behandeln. Sie können BOOST_PP_VARIADIC_SIZE verwenden, um die Größe der Argumentliste zu überprüfen und dann bedingt auf ein anderes Makro zu erweitern. Der einzige Nachteil dabei ist, dass es nicht zwischen 0 und 1 Argumenten unterscheiden kann und der Grund dafür klar wird, 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 ein Argument hat. Wir können es als zwei "Überladungsmakros" implementieren:

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

Und dann ein anderes 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 für lesbarer halten (ich bevorzuge das erste, da es Ihnen eine generelle Form für das Überladen von Makros bezüglich der Anzahl der Argumente gibt).

Es ist auch möglich, dies mit einem einzelnen Makro durch Zugreifen auf und Ändern der Liste der Variablenargumente zu tun. Dies ist jedoch weniger lesbar und für dieses Problem sehr spezifisch:

#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? Dies würde diese Lösung viel weniger schrecklich machen.

Edit: Okay, hier ist eine 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__)))) \
    /**/
10
DRayX

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

Die Schlüsselidee ist, dass es eine Möglichkeit gibt, ein Makro NUM_ARGS zu schreiben, um die Anzahl der Argumente zu zählen, die ein variadisches Makro erhalten hat. Sie können eine Variation von NUM_ARGS verwenden, um NUM_ARGS_CEILING2 zu erstellen, die Ihnen sagen kann, ob ein variadisches Makro ein Argument oder zwei oder mehr Argumente enthält. Dann können Sie Ihr Bar-Makro so schreiben, dass es NUM_ARGS_CEILING2 und CONCAT verwendet, um seine Argumente an eines der beiden Hilfsmakros zu senden: Eines, das genau ein Argument erwartet, und ein anderes, das eine variable Anzahl von Argumenten erwartet, die größer als 1 ist. 

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

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 üblich implementiert wird. Ein kurzer Hinweis, wenn das obige verwirrend erscheint: Das Ziel von CONCAT besteht darin, auf ein anderes Makro "Aufruf" zu erweitern.

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

Zwei Notizen: 

  • NUM_ARGS ist in der Anzahl der Argumente begrenzt, die es behandelt. Mein Kann nur bis zu 20 verarbeiten, obwohl die Anzahl völlig willkürlich ist. 

  • Wie gezeigt, hat NUM_ARGS eine Gefahr, dass es 1 zurückgibt, wenn 0 Argumente angegeben werden. Das Wichtigste dabei ist, dass NUM_ARGS technisch [Kommas + 1] zählt und nicht Argumente. In diesem Fall funktioniert es tatsächlich zu unserem Vorteil. _UNIMPLEMENTED1 behandelt ein leeres Token gut Und erspart uns das Schreiben von _UNIMPLEMENTED0. Gustedt hat auch eine Workaround, obwohl ich es noch nicht verwendet habe und nicht sicher bin, ob es für das, was wir hier tun, funktioniert.

4
User123abc

Ein sehr einfaches Makro, das ich zum Debugging-Drucken 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, wird keine c99-Warnung angezeigt.

Der Trick ist __DBG_INT das Hinzufügen eines Dummy-Parameters, so dass ... immer mindestens ein Argument hat und c99 erfüllt ist.

2
SimonW

Dies ist die vereinfachte Version, die ich verwende. Es basiert auf den großen Techniken der anderen Antworten, 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 weitere Parameter zu _SELECT und weitere N-Argumente hinzu. Die Argumentnamen zählen abwärts (anstelle von aufwärts), um als Erinnerung daran zu dienen, dass das zahlbasierte Argument SUFFIX in umgekehrter Reihenfolge bereitgestellt wird.

Diese Lösung behandelt 0 Argumente als ein Argument. Also funktioniert BAR() nominal, weil es sich zu _SELECT(_BAR,,N,N,N,N,1)() ausdehnt, was zu _BAR_1()() ausdehnt, was zu printf("\n") ausdehnt.

Wenn Sie möchten, können Sie mit _SELECT kreativ werden und verschiedene Makros für unterschiedliche Anzahl von Argumenten bereitstellen. Zum Beispiel haben wir hier ein LOG-Makro, das vor dem Format ein 'level'-Argument enthält. Wenn das Format fehlt, protokolliert es "(keine Nachricht)", wenn es nur ein Argument gibt, wird es über "% s" protokolliert. Andernfalls wird das Formatargument als eine 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
*/
1
ɲeuroburɳ

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!

Geht davon aus:

  • Kein Argument enthält Komma oder Klammer
  • Kein Argument enthält A ~ G (kann in hard_collide umbenannt werden)
0
l4m2

In Ihrer Situation (mindestens 1 Argument vorhanden, niemals 0) können Sie BAR als BAR(...) definieren, Jens GustedtsHAS_COMMA(...) verwenden, um ein Komma zu ermitteln, und dann entsprechend an BAR0(Fmt) oder BAR1(Fmt,...) senden.

Diese:

#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 -pedantic ohne Warnung.

0
PSkocik