Standardowa alternatywa dla sztuczki ## __ VA_ARGS__ GCC?


151

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 fmtjest 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.


3
Po co używać BARzamiast FOOw pierwszej kolejności?
GManNickG

@GMan: Na koniec dodałem przykład
jwd

5
@GMan: Przeczytaj ostatnie zdanie (:
jwd


2
@zwol najnowsza wersja przesłana do WG14 wygląda następująco , która używa nowej składni opartej na __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)
Leushenko

Odpowiedzi:


66

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.


2
Sądząc po mojej niepełnosprawności w znalezieniu rozwiązania w sieci i braku odpowiedzi tutaj, myślę, że masz rację):
jwd

2
Czy n976 jest tym, o czym mówisz? Szukałem reszty grupy roboczej C „s dokumentów na odpowiedź, ale nigdy nie znalazłem jeden. Nie było tego nawet w programie kolejnego spotkania . Jedynym innym hitem w tym temacie był komentarz Norwegii nr 4 w n868 sprzed ratyfikacji C99 (ponownie bez dalszej dyskusji).
Richard Hansen,

4
Tak, konkretnie w drugiej połowie tego. Być może dyskutowano, comp.std.cale 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ł).
zwol

1
Obawiam się, że nie mam dowodu ani nie jestem już właściwą osobą, aby spróbować go wymyślić. Napisałem połowę preprocesora GCC, ale to było ponad dziesięć lat temu i nawet wtedy nie pomyślałem o tej sztuczce z liczeniem argumentów.
zwolni

6
To rozszerzenie działa z kompilatorami clang i intel icc, a także z gcc.
ACykliczne

112

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:

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

Ta sama sztuczka służy do:

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, że FIRST_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:

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

Z dwoma lub więcej rozszerza się w następujący sposób:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. 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) lub REST_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, ONEjeśli podano tylko jeden argument, TWOORMOREjeś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żywa SELECT_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 wielokropek SELECT_10TH()należy przekazać przynajmniej 11 argumentów (norma mówi, że dla wielokropka musi być przynajmniej jeden argument). To dlatego NUM()przechodzi throwawayjako ostatni argument (bez niego, przechodząc jeden argument do NUM()spowodowałaby tylko 10 argumenty są przekazywane doSELECT_10TH() , które naruszają normy).

Wybór jednego z nich REST_HELPER_ONE()lub REST_HELPER_TWOORMORE()jest dokonywany przez połączenie REST_HELPER_z rozszerzeniemNUM(__VA_ARGS__) w REST_HELPER2(). Zauważ, że celem REST_HELPER()jest upewnienie się, że NUM(__VA_ARGS__)jest w pełni rozwinięty przed połączeniem z REST_HELPER_.

Rozszerzenie z jednym argumentem wygląda następująco:

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

Rozszerzenie z dwoma lub więcej argumentami wygląda następująco:

  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
Zauważ, że to się nie powiedzie, jeśli wywołasz BAR z 10 lub więcej argumentami i chociaż stosunkowo łatwo jest rozszerzyć na więcej argumentów, zawsze będzie miał górną granicę liczby argumentów, z którymi może sobie poradzić
Chris Dodd

2
@ChrisDodd: Dobrze. Niestety, wydaje się, że nie ma sposobu na uniknięcie ograniczenia liczby argumentów bez polegania na rozszerzeniach specyficznych dla kompilatora. Nie znam też sposobu na wiarygodne sprawdzenie, czy istnieje zbyt wiele argumentów (aby można było wydrukować przydatny komunikat o błędzie kompilatora, zamiast dziwnego błędu).
Richard Hansen

17

Nie jest to rozwiązanie ogólne, ale w przypadku printf można dodać nową linię, np .:

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

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:

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

Nie mogę uwierzyć, że C99 został zatwierdzony bez standardowego sposobu, aby to zrobić. ODPOWIEDŹ, problem istnieje również w C ++ 11.


Problem z tym dodatkowym 0 polega na tym, że trafi on do kodu, jeśli wywoła funkcję vararg. Sprawdź rozwiązanie dostarczone przez Richarda Hansena
Pavel P,

@Pavel ma rację co do drugiego przykładu, ale pierwszy działa świetnie. +1.
kirbyfan64sos

11

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:

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

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”:

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

A potem kolejne makro do przełączania się między nimi, takie jak:

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

lub

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

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:

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

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

#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
Miło wiedzieć o Boost.Preprocessor, +1. Zauważ, że 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).
Richard Hansen

1
Tak, widziałem, że twoje podejście było takie samo, jak stosowane przez Boost, ale rozwiązanie doładowania jest bardzo dobrze utrzymane i ma wiele innych naprawdę przydatnych funkcji do wykorzystania podczas tworzenia bardziej wyrafinowanych makr. Rekursja jest szczególnie fajna (i używana za kulisami w ostatnim podejściu, które używa BOOST_PP_ARRAY_ENUM).
DRayX

1
Odpowiedź Boost, która faktycznie dotyczy tagu c ! Brawo!
Justin

6

Bardzo proste makro, którego używam do debugowania drukowania:

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

Bez względu na to, ile argumentów jest przekazywanych do DBG, nie ma ostrzeżenia c99.

Sztuczka polega na __DBG_INTdodaniu fikcyjnego parametru, więc ...zawsze będzie miał co najmniej jeden argument i c99 jest spełnione.


5

Niedawno napotkałem podobny problem i wierzę, że istnieje rozwiązanie.

Główną ideą jest to, że istnieje sposób na napisanie makra NUM_ARGSzliczającego liczbę argumentów, które podano w makrze wariadycznym. Możesz użyć odmiany NUM_ARGSdo budowania NUM_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ć swoje Barmakro tak, aby używało NUM_ARGS_CEILING2iCONCAT 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:

/** 
 * 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

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

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

KROK 3:

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

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.


+1 za podniesienie sztuczki z liczeniem argumentów, -1 za bycie naprawdę trudnym do naśladowania
Richard Hansen

Komentarze, które dodałeś, były ulepszeniem, ale nadal istnieje kilka kwestii: 1. Dyskutujesz i definiujesz, NUM_ARGSale nie używasz tego. 2. Jaki jest cel UNIMPLEMENTED? 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.
Richard Hansen

8. Podajesz link do całego bloga, a nie do odpowiedniego posta. Nie mogę znaleźć posta, do którego się odnosisz. 9. Ostatni akapit jest niezręczny: ta metoda jest niejasna; dlatego nikt wcześniej nie opublikował poprawnego rozwiązania. Ponadto, jeśli to działa i jest zgodne ze standardem, odpowiedź Zacka musi być błędna. 10. Powinieneś zdefiniować CONCAT()- nie zakładaj, że czytelnicy wiedzą, jak to działa.
Richard Hansen

(Proszę nie interpretować tej opinii jako ataku - naprawdę chciałem zagłosować za Twoją odpowiedź, ale nie czułem się komfortowo, chyba że było to łatwiejsze do zrozumienia. Jeśli możesz poprawić przejrzystość swojej odpowiedzi, popieraj swoje i usuwaj moje.)
Richard Hansen

2
Nigdy bym nie pomyślał o takim podejściu i napisałem mniej więcej połowę obecnego preprocesora GCC! To powiedziawszy, nadal mówię, że „nie ma standardowego sposobu na uzyskanie takiego efektu”, ponieważ zarówno twoja, jak i technika Richarda narzucają górny limit liczby argumentów w makrze.
zwol

2

To jest uproszczona wersja, której używam. Opiera się na wspaniałych technikach innych odpowiedzi tutaj, a jest ich tak wiele:

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

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 _SELECTi więcej Nargumentó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 _SELECTwykazać 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.

#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
*/

Wciąż powoduje to wyświetlenie ostrzeżenia po kompilacji z opcją -pedantic.
PSkocik

1

W twojej sytuacji (obecny co najmniej 1 argument, nigdy 0), możesz zdefiniować BARjako BAR(...), użyj Jensa Gustedta HAS_COMMA(...) do wykrycia przecinka, a następnie wyślij do BAR0(Fmt)lub BAR1(Fmt,...)odpowiednio.

To:

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

kompiluje się z -pedanticbez ostrzeżenia.


0

C (gcc) , 762 bajty

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

Wypróbuj online!

Zakłada:

  • Żaden argument nie zawiera przecinka ani nawiasu
  • Żaden argument nie zawiera A~ G(można zmienić nazwę na hard_collide)

no arg contain commaOgraniczenie można obejść poprzez sprawdzenie multi po jakichś kilku przejściach, ale no bracketnadal istnieje
l4m2

-2

Standardowym rozwiązaniem jest użycie FOOzamiast BAR. 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ąc FOO„zwykle” po prostu działa.


1
Pytanie brzmiało: „czy istnieje zgodny ze standardami sposób uzyskania takiego zachowania?”
Marsh Ray

2
Pytanie zawierało uzasadnienie dla nieużywania FOO od wieków.
Pavel Šimerda
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.