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 fmt
handelt 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.
BAR
statt überhaupt verwendenFOO
?__VA_OPT__
Schlüsselwort basiert . Dies wurde bereits von C ++ "übernommen" , daher gehe ich davon aus, dass C diesem Beispiel folgen wird. (Antworten:
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.quelle
comp.std.c
aber 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).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 :Der gleiche Trick wird verwendet, um:
__VA_ARGS__
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
throwaway
Argument stellt sicher, dassFIRST_HELPER()
zwei Argumente erhalten werden, was erforderlich ist, weil die...
mindestens eines benötigt wird. Mit einem Argument wird es wie folgt erweitert:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Mit zwei oder mehr wird es wie folgt erweitert:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
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) oderREST_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,ONE
wenn nur ein Argument angegeben wird,TWOORMORE
wenn 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 dasSELECT_10TH()
Makro, um die Anzahl der Argumente zu bestimmen. Wie der Name schon sagt, wirdSELECT_10TH()
einfach auf das 10. Argument erweitert. Aufgrund der AuslassungspunkteSELECT_10TH()
müssen mindestens 11 Argumente übergeben werden (der Standard besagt, dass mindestens ein Argument für die Auslassungspunkte vorhanden sein muss). DeshalbNUM()
gehtthrowaway
als letztes Argument (ohne sie zu einem Argument übergebenNUM()
würde in nur 10 Argumente übergeben werdenSELECT_10TH()
, die den Standard verletzen würde).Die Auswahl von entweder
REST_HELPER_ONE()
oderREST_HELPER_TWOORMORE()
erfolgt durch VerkettenREST_HELPER_
mit der Erweiterung vonNUM(__VA_ARGS__)
inREST_HELPER2()
. Beachten Sie, dass der Zweck vonREST_HELPER()
besteht, sicherzustellen, dassNUM(__VA_ARGS__)
es vollständig erweitert ist, bevor es verkettet wirdREST_HELPER_
.Die Erweiterung mit einem Argument lautet wie folgt:
REST(firstarg)
REST_HELPER(NUM(firstarg), firstarg)
REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
REST_HELPER2(ONE, firstarg)
REST_HELPER_ONE(firstarg)
Die Erweiterung mit zwei oder mehr Argumenten erfolgt wie folgt:
REST(firstarg, secondarg, thirdarg)
REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
, secondarg, thirdarg
quelle
Keine allgemeine Lösung, aber im Fall von printf können Sie eine neue Zeile anhängen wie:
Ich glaube, es ignoriert alle zusätzlichen Argumente, auf die in der Formatzeichenfolge nicht verwiesen wird. Sie könnten also wahrscheinlich sogar davonkommen mit:
Ich kann nicht glauben, dass C99 ohne eine Standardmethode genehmigt wurde. AFAICT das Problem besteht auch in C ++ 11.
quelle
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:
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:
Und dann noch ein Makro, um zwischen ihnen zu wechseln, wie zum Beispiel:
oder
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:
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):
quelle
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).Ein sehr einfaches Makro, das ich für den Debug-Druck verwende:
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.quelle
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_ARGS
um die Anzahl der Argumente zu zählen, die einem variadischen Makro gegeben werden. Sie können eine Variation vonNUM_ARGS
zum Erstellen verwendenNUM_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 IhrBar
Makro so schreiben , dass esNUM_ARGS_CEILING2
und 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 istBAR
:SCHRITT 1:
SCHRITT 1.5:
Schritt 2:
SCHRITT 3:
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.
quelle
NUM_ARGS
, verwenden sie jedoch nicht. 2. Was ist der Zweck vonUNIMPLEMENTED
? 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.CONCAT()
- nehmen Sie nicht an, dass die Leser wissen, wie es funktioniert.Dies ist die vereinfachte Version, die ich verwende. Es basiert auf den großartigen Techniken der anderen Antworten hier, so viele Requisiten an sie:
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
_SELECT
und mehrN
Argumente 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,
_SELECT
indem 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.quelle
In Ihrer Situation (mindestens 1 Argument vorhanden, nie 0), können Sie festlegen ,
BAR
wieBAR(...)
, verwenden Jens Gustedt istHAS_COMMA(...)
ein Komma zu erkennen und dann zu versendenBAR0(Fmt)
oderBAR1(Fmt,...)
entsprechend.Dies:
Kompiliert mit
-pedantic
ohne Vorwarnung.quelle
C (gcc) , 762 Bytes
Probieren Sie es online aus!
Geht davon aus:
A
~G
(kann in hard_collide umbenannt werden)quelle
no arg contain comma
Einschränkung kann umgangen werden, indem nach einigen weiteren Durchgängen Multi überprüft wird, aberno bracket
immer noch vorhanden istDie Standardlösung ist die Verwendung von
FOO
anstelle vonBAR
. 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 mitFOO
"normalerweise". funktioniert einfach.quelle