Istnieje dobrze znany problem z pustymi argumentami dla makr wariadycznych w C99.
przykład:
#define FOO(...) printf(__VA_ARGS__)
#define BAR(fmt, ...) printf(fmt, __VA_ARGS__)
FOO("this works fine");
BAR("this breaks!");
Użycie BAR()
powyższego jest rzeczywiście nieprawidłowe zgodnie ze standardem C99, ponieważ rozszerzy się do:
printf("this breaks!",);
Zwróć uwagę na końcowy przecinek - nie działa.
Niektóre kompilatory (np. Visual Studio 2010) po cichu pozbędą się tego końcowego przecinka. Inne kompilatory (np .: GCC) obsługują wstawianie ##
przed __VA_ARGS__
, na przykład:
#define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__)
Ale czy istnieje sposób zgodny ze standardami, aby uzyskać takie zachowanie? Może używasz wielu makr?
Obecnie ##
wersja wydaje się dość dobrze obsługiwana (przynajmniej na moich platformach), ale naprawdę wolałbym użyć rozwiązania zgodnego ze standardami.
Zapobiegawczy: wiem, że mógłbym po prostu napisać małą funkcję. Próbuję to zrobić za pomocą makr.
Edycja : Oto przykład (choć prosty), dlaczego chciałbym używać 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);
Spowoduje to automatyczne dodanie nowej linii do moich instrukcji logowania BAR (), przy założeniu, że fmt
jest to ciąg znaków C w podwójnym cudzysłowie. NIE drukuje nowej linii jako oddzielnej funkcji printf (), co jest korzystne, jeśli rejestrowanie jest buforowane wierszami i pochodzi z wielu źródeł asynchronicznie.
BAR
zamiastFOO
w pierwszej kolejności?__VA_OPT__
słowie kluczowym. Zostało to już „przyjęte” przez C ++, więc spodziewam się, że C pójdzie w ich ślady. (nie wiem, czy to oznacza, że został przyspieszony do C ++ 17, czy też jest ustawiony na C ++ 20)Odpowiedzi:
Możliwe jest uniknięcie używania
,##__VA_ARGS__
rozszerzenia GCC, jeśli chcesz zaakceptować zakodowany na stałe górny limit liczby argumentów, które możesz przekazać do swojego makra wariadycznego, jak opisano w odpowiedzi Richarda Hansena na to pytanie . Jeśli jednak nie chcesz mieć takiego ograniczenia, to według mojej najlepszej wiedzy nie jest możliwe używanie tylko funkcji preprocesora określonych w C99; musisz użyć jakiegoś rozszerzenia języka. clang i icc przyjęły to rozszerzenie GCC, ale MSVC nie.W 2001 roku napisałem rozszerzenie GCC do standaryzacji (i powiązane rozszerzenie, które pozwala na użycie nazwy innej niż
__VA_ARGS__
parametr rest) w dokumencie N976 , ale nie otrzymałem żadnej odpowiedzi od komitetu; Nie wiem nawet, czy ktoś to przeczytał. W 2016 roku została ponownie zaproponowana w N2023 i zachęcam każdego, kto wie, jak ta propozycja da nam znać w komentarzach.źródło
comp.std.c
ale nie udało mi się znaleźć żadnego w Grupach dyskusyjnych Google; z pewnością nigdy nie zwrócił na to uwagi właściwej komisji (a jeśli tak, to nikt mi o tym nie powiedział).Istnieje sztuczka polegająca na liczeniu argumentów, której możesz użyć.
Oto jeden zgodny ze standardami sposób implementacji drugiego
BAR()
przykładu w pytaniu jwd:Ta sama sztuczka służy do:
__VA_ARGS__
Wyjaśnienie
Strategia polega na rozdzieleniu
__VA_ARGS__
na pierwszy argument i resztę (jeśli w ogóle). Dzięki temu możliwe jest wstawianie rzeczy po pierwszym argumencie, ale przed drugim (jeśli jest obecny).FIRST()
To makro po prostu rozwija się do pierwszego argumentu, odrzucając resztę.
Implementacja jest prosta. Te
throwaway
, zapewnia argument, żeFIRST_HELPER()
wystąpią dwa argumenty, które są wymagane, ponieważ...
potrzebuje co najmniej jednego. Z jednym argumentem rozwija się w następujący sposób:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Z dwoma lub więcej rozszerza się w następujący sposób:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
firstarg
REST()
To makro interpretuje wszystko oprócz pierwszego argumentu (włączając przecinek po pierwszym argumencie, jeśli jest więcej niż jeden argument).
Implementacja tego makra jest znacznie bardziej skomplikowana. Ogólna strategia polega na policzeniu liczby argumentów (jednego lub więcej niż jeden), a następnie rozwinięciu do albo
REST_HELPER_ONE()
(jeśli podano tylko jeden argument) lubREST_HELPER_TWOORMORE()
(jeśli podano dwa lub więcej argumentów).REST_HELPER_ONE()
po prostu rozwija się do zera - po pierwszym nie ma argumentów, więc pozostałe argumenty to pusty zbiór.REST_HELPER_TWOORMORE()
jest również prosta - rozwija się do przecinka, po którym następuje wszystko oprócz pierwszego argumentu.Argumenty są liczone za pomocą
NUM()
makra. To makro rozwija się do,ONE
jeśli podano tylko jeden argument,TWOORMORE
jeśli podano od dwóch do dziewięciu argumentów, i przerywa, jeśli podano 10 lub więcej argumentów (ponieważ rozwija się do dziesiątego argumentu).NUM()
Makro używaSELECT_10TH()
makra, aby określić liczbę argumentów. Jak sama nazwa wskazuje,SELECT_10TH()
po prostu rozwija się do dziesiątego argumentu. Ze względu na wielokropekSELECT_10TH()
należy przekazać przynajmniej 11 argumentów (norma mówi, że dla wielokropka musi być przynajmniej jeden argument). To dlategoNUM()
przechodzithrowaway
jako ostatni argument (bez niego, przechodząc jeden argument doNUM()
spowodowałaby tylko 10 argumenty są przekazywane doSELECT_10TH()
, które naruszają normy).Wybór jednego z nich
REST_HELPER_ONE()
lubREST_HELPER_TWOORMORE()
jest dokonywany przez połączenieREST_HELPER_
z rozszerzeniemNUM(__VA_ARGS__)
wREST_HELPER2()
. Zauważ, że celemREST_HELPER()
jest upewnienie się, żeNUM(__VA_ARGS__)
jest w pełni rozwinięty przed połączeniem zREST_HELPER_
.Rozszerzenie z jednym argumentem wygląda następująco:
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)
Rozszerzenie z dwoma lub więcej argumentami wygląda następująco:
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
źródło
Nie jest to rozwiązanie ogólne, ale w przypadku printf można dodać nową linię, np .:
Uważam, że ignoruje wszelkie dodatkowe argumenty, do których nie ma odniesienia w ciągu formatu. Więc prawdopodobnie możesz nawet uciec z:
Nie mogę uwierzyć, że C99 został zatwierdzony bez standardowego sposobu, aby to zrobić. ODPOWIEDŹ, problem istnieje również w C ++ 11.
źródło
Istnieje sposób na rozwiązanie tego konkretnego przypadku za pomocą czegoś takiego jak Boost.Preprocessor . Możesz użyć BOOST_PP_VARIADIC_SIZE, aby sprawdzić rozmiar listy argumentów, a następnie warunkowo rozwinąć do innego makra. Jedyną wadą tego rozwiązania jest to, że nie można odróżnić argumentu od 0 do 1, a przyczyna tego staje się jasna, gdy weźmiesz pod uwagę następujące kwestie:
Pusta lista argumentów makr w rzeczywistości składa się z jednego argumentu, który okazał się być pusty.
W tym przypadku mamy szczęście, ponieważ żądane makro zawsze ma co najmniej 1 argument, możemy je zaimplementować jako dwa makra „przeciążające”:
A potem kolejne makro do przełączania się między nimi, takie jak:
lub
Cokolwiek uznasz za bardziej czytelne (wolę pierwszą, ponieważ daje ogólny formularz do przeciążania makr na podstawie liczby argumentów).
Można to również zrobić za pomocą jednego makra, uzyskując dostęp do listy argumentów zmiennych i modyfikując ją, ale jest to znacznie mniej czytelne i jest bardzo specyficzne dla tego problemu:
Dlaczego nie ma BOOST_PP_ARRAY_ENUM_TRAILING? Dzięki temu to rozwiązanie byłoby znacznie mniej straszne.
Edycja: OK, oto BOOST_PP_ARRAY_ENUM_TRAILING i wersja, która go używa (to jest teraz moje ulubione rozwiązanie):
źródło
BOOST_PP_VARIADIC_SIZE()
używa tej samej sztuczki z liczeniem argumentów, którą udokumentowałem w mojej odpowiedzi, i ma to samo ograniczenie (zepsuje się, jeśli przekażesz więcej niż określoną liczbę argumentów).Bardzo proste makro, którego używam do debugowania drukowania:
Bez względu na to, ile argumentów jest przekazywanych do DBG, nie ma ostrzeżenia c99.
Sztuczka polega na
__DBG_INT
dodaniu fikcyjnego parametru, więc...
zawsze będzie miał co najmniej jeden argument i c99 jest spełnione.źródło
Niedawno napotkałem podobny problem i wierzę, że istnieje rozwiązanie.
Główną ideą jest to, że istnieje sposób na napisanie makra
NUM_ARGS
zliczającego liczbę argumentów, które podano w makrze wariadycznym. Możesz użyć odmianyNUM_ARGS
do budowaniaNUM_ARGS_CEILING2
, która może ci powiedzieć, czy makro wariadyczne ma 1 argument, czy 2 lub więcej argumentów. Następnie możesz napisać swojeBar
makro tak, aby używałoNUM_ARGS_CEILING2
iCONCAT
wysyłało swoje argumenty do jednego z dwóch makr pomocniczych: jednego, które oczekuje dokładnie 1 argumentu, a drugiego, które oczekuje zmiennej liczby argumentów większej niż 1.Oto przykład, w którym używam tej sztuczki, aby napisać makro
UNIMPLEMENTED
, które jest bardzo podobne doBAR
:KROK 1:
KROK 1.5:
Krok 2:
KROK 3:
Gdzie CONCAT jest zaimplementowany w zwykły sposób. Jako krótka wskazówka, jeśli powyższe wydaje się mylące: celem CONCAT jest rozwinięcie do kolejnego „wywołania” makra.
Zwróć uwagę, że sam NUM_ARGS nie jest używany. Dodałem go tylko, aby zilustrować tutaj podstawową sztuczkę. Zobacz blog Jensa Gustedta P99, aby zapoznać się z przyjemnym traktowaniem tego.
Dwie uwagi:
NUM_ARGS jest ograniczona liczbą argumentów, które obsługuje. Mój może obsłużyć maksymalnie 20, chociaż liczba jest całkowicie dowolna.
NUM_ARGS, jak pokazano, ma pułapkę polegającą na tym, że zwraca 1, gdy ma 0 argumentów. Istota tego jest taka, że NUM_ARGS technicznie liczy [przecinki + 1], a nie argumenty. W tym konkretnym przypadku działa to na naszą korzyść. _UNIMPLEMENTED1 bez problemu obsłuży pusty token i oszczędza nam konieczności pisania _UNIMPLEMENTED0. Gustedt również ma obejście tego problemu, chociaż go nie używałem i nie jestem pewien, czy zadziała w tym, co tutaj robimy.
źródło
NUM_ARGS
ale nie używasz tego. 2. Jaki jest celUNIMPLEMENTED
? 3. Nigdy nie rozwiązujesz przykładowego problemu w pytaniu. 4. Przechodzenie po rozszerzeniu krok po kroku zilustruje jego działanie i wyjaśni rolę każdego makra pomocniczego. 5. Omawianie 0 argumentów jest rozpraszające; PO pytał o zgodność ze standardami, a 0 argumentów jest zabronionych (C99 6.10.3p4). 6. Krok 1.5? Dlaczego nie krok 2? 7. „Kroki” oznaczają czynności wykonywane sekwencyjnie; to tylko kod.CONCAT()
- nie zakładaj, że czytelnicy wiedzą, jak to działa.To jest uproszczona wersja, której używam. Opiera się na wspaniałych technikach innych odpowiedzi tutaj, a jest ich tak wiele:
Otóż to.
Podobnie jak w przypadku innych rozwiązań jest to ograniczone do liczby argumentów makra. Aby obsługiwać więcej, dodaj więcej parametrów
_SELECT
i więcejN
argumentów. Nazwy argumentów odliczają w dół (zamiast w górę), aby służyć jako przypomnienie, że oparte na zliczaniuSUFFIX
argument jest podawany w odwrotnej kolejności.To rozwiązanie traktuje 0 argumentów tak, jakby był to 1 argument. Tak więc
BAR()
nominalnie „działa”, ponieważ rozszerza się do_SELECT(_BAR,,N,N,N,N,1)()
, które rozszerza się do_BAR_1()()
, które rozszerza się doprintf("\n")
.Jeśli chcesz, możesz
_SELECT
wykazać się kreatywnością, używając i udostępniając różne makra dla różnej liczby argumentów. Na przykład mamy tutaj makro LOG, które przyjmuje argument „level” przed formatem. Jeśli brakuje formatu, rejestruje "(brak komunikatu)", jeśli jest tylko 1 argument, zapisze go przez "% s", w przeciwnym razie potraktuje argument format jako łańcuch formatu printf dla pozostałych argumentów.źródło
W twojej sytuacji (obecny co najmniej 1 argument, nigdy 0), możesz zdefiniować
BAR
jakoBAR(...)
, użyj Jensa GustedtaHAS_COMMA(...)
do wykrycia przecinka, a następnie wyślij doBAR0(Fmt)
lubBAR1(Fmt,...)
odpowiednio.To:
kompiluje się z
-pedantic
bez ostrzeżenia.źródło
C (gcc) , 762 bajty
Wypróbuj online!
Zakłada:
A
~G
(można zmienić nazwę na hard_collide)źródło
no arg contain comma
Ograniczenie można obejść poprzez sprawdzenie multi po jakichś kilku przejściach, aleno bracket
nadal istniejeStandardowym rozwiązaniem jest użycie
FOO
zamiastBAR
. Jest kilka dziwnych przypadków, w których zmiana kolejności argumentów prawdopodobnie nie jest dla ciebie możliwa (chociaż założę się, że ktoś może wymyślić sprytne sztuczki do demontażu i ponownego złożenia__VA_ARGS__
warunkowego w oparciu o liczbę argumentów!), Ale ogólnie używającFOO
„zwykle” po prostu działa.źródło