Parametry opcjonalne z makrami C ++


106

Czy istnieje sposób na uzyskanie opcjonalnych parametrów w makrach C ++? Przydałoby się też jakieś przeciążenie.


1
To samo dla C: stackoverflow.com/questions/11761703/… Powinien być taki sam, ponieważ preprocesory są w zasadzie takie same: stackoverflow.com/questions/5085533/ ...
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Być może przeciążenia funkcji, parametry domyślne, szablony wariadyczne lub ewentualnie nazwany idiom parametrów są tym, czego szukasz
— smoothware

Proszę zaktualizuj wybraną odpowiedź do tych, które uzyskały wysokie głosy, podając rzeczywiste rozwiązania, a nie te, które miały niskie głosy, mówiącNo you can't
— Albert Renshaw

Odpowiedzi:


158

Oto jeden sposób, aby to zrobić. Używa listy argumentów dwukrotnie, najpierw do utworzenia nazwy makra pomocnika, a następnie do przekazania argumentów do tego makra pomocniczego. Używa standardowej sztuczki, aby policzyć liczbę argumentów do makra.

enum
{
    plain = 0,
    bold = 1,
    italic = 2
};

void PrintString(const char* message, int size, int style)
{
}

#define PRINT_STRING_1_ARGS(message)              PrintString(message, 0, 0)
#define PRINT_STRING_2_ARGS(message, size)        PrintString(message, size, 0)
#define PRINT_STRING_3_ARGS(message, size, style) PrintString(message, size, style)

#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define PRINT_STRING_MACRO_CHOOSER(...) \
    GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \
                PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )

#define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main(int argc, char * const argv[])
{
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 18);
    PRINT_STRING("Hello, World!", 18, bold);

    return 0;
}

Ułatwia to wywołującemu makro, ale nie piszącemu.


2
To całkiem fajne, ale nie sądzę, żeby zadziałało, gdybym właśnie wykonał PRINT_STRING. W takim przypadku nie byłoby domyślnego wydruku (i tak właśnie jest w przypadku, którego chcę użyć). Wciąż +1 za naprawdę fajne.
— Cenoc

2
działa dla mnie w gcc (i jest bardzo sprytne!) :-), ale nie działa dla mnie w Visual Studio :-(
— Tim Gradwell

4
@TimGradwell - jest to spowodowane błędem w kompilatorze MSVC, który potwierdzili, ale nie naprawili go od prawie dekady. Jednak dostępne są obejścia .
— BeeOnRope

Sprytne, ale nie działa dla opcjonalnych zmiennych argumentów makr z powodu „wypchnięcia” rzeczy, które masz w „GET_4th_ARG”.
— Searchengine

czy to w PRINT_STRING_MACRO_CHOOSERogóle potrzebne? Czy mogę bezpośrednio zastąpić jego wewnętrznym ciałem i nazwać to wszystko (__VA_ARGS__)?
— Herrgott

85

Z wielkim szacunkiem dla Dereka Ledbettera za jego odpowiedź - i z przeprosinami za przywrócenie starego pytania.

Zrozumienie tego, co robił, i zebranie w innym miejscu zdolności poprzedzającej __VA_ARGS__z, ##pozwoliło mi wymyślić odmianę ...

// The multiple macros that you would need anyway [as per: Crazy Eddie]
#define XXX_0()                     <code for no arguments> 
#define XXX_1(A)                    <code for one argument> 
#define XXX_2(A,B)                  <code for two arguments> 
#define XXX_3(A,B,C)                <code for three arguments> 
#define XXX_4(A,B,C,D)              <code for four arguments>  

// The interim macro that simply strips the excess and ends up with the required macro
#define XXX_X(x,A,B,C,D,FUNC, ...)  FUNC  

// The macro that the programmer uses 
#define XXX(...)                    XXX_X(,##__VA_ARGS__,\
                                          XXX_4(__VA_ARGS__),\
                                          XXX_3(__VA_ARGS__),\
                                          XXX_2(__VA_ARGS__),\
                                          XXX_1(__VA_ARGS__),\
                                          XXX_0(__VA_ARGS__)\
                                         ) 

Dla nie-ekspertów takich jak ja, którzy natkną się na odpowiedź, ale nie do końca rozumieją, jak to działa, przejdę przez rzeczywiste przetwarzanie, zaczynając od następującego kodu ...

XXX();
XXX(1); 
XXX(1,2); 
XXX(1,2,3); 
XXX(1,2,3,4); 
XXX(1,2,3,4,5);      // Not actually valid, but included to show the process 

Staje się...

XXX_X(, XXX_4(), XXX_3(),  XXX_2(),    XXX_1(),      XXX_0()         );
XXX_X(, 1,       XXX_4(1), XXX_3(1),   XXX_2(1),     XXX_1(1),       XXX_0(1)          );
XXX_X(, 1,       2,        XXX_4(1,2), XXX_3(1,2),   XXX_2(1,2),     XXX_1(1,2),       XXX_0(1,2)        );
XXX_X(, 1,       2,        3,          XXX_4(1,2,3), XXX_3(1,2,3),   XXX_2(1,2,3),     XXX_1(1,2,3),     XXX_0(1,2,3)      );
XXX_X(, 1,       2,        3,          4,            XXX_4(1,2,3,4), XXX_3(1,2,3,4),   XXX_2(1,2,3,4),   XXX_1(1,2,3,4),   XXX_0(1,2,3,4)    );
XXX_X(, 1,       2,        3,          4,            5,              XXX_4(1,2,3,4,5), XXX_3(1,2,3,4,5), XXX_2(1,2,3,4,5), XXX_1(1,2,3,4,5), XXX_0(1,2,3,4,5) );

Co staje się dopiero szóstym argumentem ...

XXX_0(); 
XXX_1(1); 
XXX_2(1,2); 
XXX_3(1,2,3); 
XXX_4(1,2,3,4); 
5; 

PS: Usuń #define dla XXX_0, aby uzyskać błąd kompilacji [tj .: jeśli opcja bez argumentów jest niedozwolona].

PPS: Byłoby miło, gdyby nieprawidłowe sytuacje (np .: 5) były czymś, co daje programiście wyraźniejszy błąd kompilacji!

PPPS: Nie jestem ekspertem, więc bardzo się cieszę, słysząc komentarze (dobre, złe lub inne)!


3
Możesz otrzymać wyraźny błąd kompilacji, jeśli przekonwertowałeś wybrany argument, który ma być nazwą MAKRO na łańcuch za pomocą # (znak krzyżyka) i porównałeś jego pierwsze n znaków z oczekiwanym przedrostkiem, a jeśli nie ma dopasowania, wypisano informacyjny błąd.
— AturSams,

1
Wow, nie wiem, czy to działa, ale jest przynajmniej bardzo kreatywne!
— Limited Atonement

4
dlaczego pierwszy argument jest zawsze pusty? dlaczego nie możemy tego po prostu pominąć: XXX_X(,##__VA_ARGS__,` ... XXX_X (, XXX_4 (), XXX_3 (), XXX_2 (), XXX_1 (), XXX_0 ()); `
— rahman

2
Ważny jest pusty pierwszy argument (przecinek). ## __ VA_ARGS__, jeśli jest poprzedzony przecinkiem - usuwa przecinek, jeśli ## __ VA_ARGS__ rozwinie się do zera. Możesz to zobaczyć w przykładzie "Staje się ...", ponieważ pierwsza linia (bez argumentów) ma tylko 6 parametrów, ale reszta otrzymuje 7. Ta sztuczka zapewnia, że ​​sytuacja bez argumentów działa
— David Sorkovsky

@Eric - jest to spowodowane błędem w kompilatorach Microsoft, ale możesz zobaczyć to pytanie w celu obejścia problemu.
— BeeOnRope

31

Makra C ++ nie zmieniły się od C. Ponieważ C nie ma przeciążenia i domyślnych argumentów funkcji, z pewnością nie ma ich dla makr. Odpowiadając na pytanie: nie, te funkcje nie istnieją w przypadku makr. Jedyną opcją jest zdefiniowanie wielu makr o różnych nazwach (lub nieużywanie makr w ogóle).

Na marginesie: w C ++ za dobrą praktykę uważa się jak największe odejście od makr. Jeśli potrzebujesz takich funkcji, istnieje duża szansa, że ​​nadużywasz makr.


4
Zwróć uwagę, że powodem, dla którego niemożliwe jest „przeciążenie” makr, jest to, że nie mają one żadnych typów. Makra są po prostu rozwijane.
— mk12,

2
Mimo, że korzystać z makr w jak najmniejszym stopniu, stwierdziliśmy, że debugowanie poprzez wyjście śledzenia dostaje trochę łatwiej z rzeczy, jak __FILE__i __LINE__i takie ...
— Christian Severin

niezbyt dobra odpowiedź. to jest dobra odpowiedź: stackoverflow.com/q/27049491/893406
— v.oddou

Kompilacja warunkowa i debugowanie / rejestrowanie to domena, w której makra są naprawdę przydatne i legalne. Wie o tym każdy poważny programista. Dobrą praktyką jest unikanie używania makr do definiowania stałych i robienie zwariowanych czynności związanych z kodowaniem na poziomie C w celu tworzenia szablonów kontenerów. Chciałbym, żeby C ++ dodał więcej funkcji do makr. Są prostopadłe do szablonów. Oczywiście najlepsze byłyby kodele, które pozwalają mi dodawać generatory do kompilatora dla języka specyficznego dla domeny (aspekty).
— Lothar

1
Myślę też, że to nie jest dobra odpowiedź, ponieważ makro jest czymś zupełnie innym niż jakakolwiek opcja języka C ++, ponieważ zostanie obsłużona PRZED kompilatorem. Możesz więc robić inne rzeczy i żaden kompilator ani konsolidator nie musi optymalizować kodu, ponieważ być może nie jest to optymalizacja.
— alabamajack

26

Z wielkim szacunkiem dla Dereka Ledbettera , Davida Sorkovsky'ego , Syphorlate za ich odpowiedzi, wraz z genialną metodą wykrywania pustych argumentów makro Jensa Gustedta w

https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

w końcu wyszedłem z czymś, co zawiera wszystkie sztuczki, tak że rozwiązanie

  1. Używa tylko standardowych makr C99, aby osiągnąć przeciążenie funkcji, bez rozszerzenia GCC / CLANG / MSVC (tj. Połykanie przecinków przez określone wyrażenie , ##__VA_ARGS__dla GCC / CLANG i niejawne połykanie przez ##__VA_ARGS__dla MSVC). Więc --std=c99jeśli chcesz, możesz przekazać brakujące informacje do swojego kompilatora =)
  2. Działa dla zerowych argumentów , a także dla nieograniczonej liczby argumentów , jeśli rozszerzysz je dalej, aby dopasować je do swoich potrzeb
  3. Działa rozsądnie na wielu platformach , przynajmniej przetestowany pod kątem

    • GNU / Linux + GCC (GCC 4.9.2 na CentOS 7.0 x86_64)
    • GNU / Linux + CLANG / LLVM , (CLANG / LLVM 3.5.0 na CentOS 7.0 x86_64)
    • OS X + Xcode (XCode 6.1.1 w systemie OS X Yosemite 10.10.1)
    • Windows + Visual Studio (Visual Studio 2013 Update 4 w systemie Windows 7 SP1 64 bity)

Dla leniwców po prostu przejdź do ostatniego z tego postu, aby skopiować źródło. Poniżej znajduje się szczegółowe wyjaśnienie, które, miejmy nadzieję, pomoże i zainspiruje wszystkich szukających generała__VA_ARGS__ rozwiązań, takich jak ja. =)

Oto jak to działa. Najpierw zdefiniować przeciążony „funkcja” widoczne dla użytkowników, nazwałem go create, a związane rzeczywista definicja funkcji realCreate, a makro definicje z różną liczbą argumentów CREATE_2, CREATE_1, CREATE_0, jak pokazano poniżej:

#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

MACRO_CHOOSER(__VA_ARGS__)Część ostatecznie postanawia makro nazwy definicji, a druga (__VA_ARGS__)część obejmuje swoje listy parametrów. Tak więc wywołanie użytkownika do jest create(10)rozstrzygane na CREATE_1(10), CREATE_1część pochodzi z MACRO_CHOOSER(__VA_ARGS__), a(10) część z drugiej (__VA_ARGS__).

MACRO_CHOOSERStosuje podstęp, że jeśli __VA_ARGS__jest pusta, następujące wyrażenie jest łączony do ważnej rozmowy przez makro preprocesora:

NO_ARG_EXPANDER __VA_ARGS__ ()  // simply shrinks to NO_ARG_EXPANDER()

Genialnie możemy zdefiniować to wynikowe wywołanie makra jako

#define NO_ARG_EXPANDER() ,,CREATE_0

Zwróć uwagę na dwa przecinki, zostaną one wkrótce wyjaśnione. Kolejne przydatne makro to

#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())

więc wezwania

create();
create(10);
create(20, 20);

są faktycznie rozszerzone do

CHOOSE_FROM_ARG_COUNT(,,CREATE_0)();
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 10 ())(10);
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 20, 20 ())(20, 20);

Jak sugeruje nazwa makra, później policzymy liczbę argumentów. Oto kolejna sztuczka: preprocesor wykonuje tylko prostą zamianę tekstu. Wyprowadza liczbę argumentów wywołania makra jedynie z liczby przecinków, które widzi w nawiasach. Rzeczywiste „argumenty” oddzielone przecinkami nie muszą mieć prawidłowej składni. Może to być dowolny tekst. To znaczy, że w powyższym przykładzie NO_ARG_EXPANDER 10 ()jest liczony jako 1 argument dla wywołania środkowego.NO_ARG_EXPANDER 20i 20 ()są liczone jako 2 argumenty odpowiednio dla dolnego wywołania.

Jeśli użyjemy następujących makr pomocniczych, aby je dalej rozwinąć

##define CHOOSE_FROM_ARG_COUNT(...) \
  FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define FUNC_RECOMPOSER(argsWithParentheses) \
  FUNC_CHOOSER argsWithParentheses

Końcowe ,po CREATE_1jest obejściem dla GCC / CLANG, pomijając (fałszywie dodatni) błąd mówiący o tym ISO C99 requires rest arguments to be usedpodczas przekazywania -pedanticdo kompilatora. Jest FUNC_RECOMPOSERto obejście dla MSVC lub nie może poprawnie policzyć liczby argumentów (tj. Przecinków) w nawiasach przy wywołaniach makr. Wyniki są dalej rozwiązywane do

FUNC_CHOOSER (,,CREATE_0, CREATE_2, CREATE_1, )();
FUNC_CHOOSER (NO_ARG_EXPANDER 10 (), CREATE_2, CREATE_1, )(10);
FUNC_CHOOSER (NO_ARG_EXPANDER 20, 20 (), CREATE_2, CREATE_1, )(20, 20);

Jak zapewne widzieliście orle oczy, ostatnim jedynym krokiem, jakiego potrzebujemy, jest zastosowanie standardowej sztuczki zliczania argumentów, aby ostatecznie wybrać żądane nazwy wersji makr:

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3

który rozwiązuje wyniki na

CREATE_0();
CREATE_1(10);
CREATE_2(20, 20);

i na pewno daje nam pożądane, rzeczywiste wywołania funkcji:

realCreate(0, 0);
realCreate(10, 10);
realCreate(20, 20);

Podsumowując wszystko razem, z pewnym przegrupowaniem instrukcji dla lepszej czytelności, całe źródło dwuargumentowego przykładu jest tutaj:

#include <stdio.h>

void realCreate(int x, int y)
{
  printf("(%d, %d)\n", x, y);
}

#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)

#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define NO_ARG_EXPANDER() ,,CREATE_0
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())
#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

int main()
{
  create();
  create(10);
  create(20, 20);
  //create(30, 30, 30);  // Compilation error
  return 0;
}

Chociaż jest to skomplikowane, brzydkie, obciążające programistę API, pojawia się rozwiązanie do przeciążania i ustawiania opcjonalnych parametrów funkcji C / C ++ dla nas szalonych ludzi. Korzystanie z wychodzących przeciążonych interfejsów API staje się bardzo przyjemne i przyjemne. =)

Jeśli istnieje jakiekolwiek dalsze uproszczenie tego podejścia, proszę o kontakt pod adresem

https://github.com/jason-deng/C99FunctionOverload

Jeszcze raz specjalne podziękowania dla wszystkich wspaniałych ludzi, którzy zainspirowali mnie i doprowadzili do wykonania tego dzieła! =)


3
Jak rozszerzyć to na 3 lub 4 funkcje?
— Phylliida

@Phylliida ideone.com/jD0Hm5 - obsługiwane od zera do pięciu argumentów.
— xx

9

Dla każdego, kto szuka rozwiązania VA_NARGS, które współpracuje z Visual C ++. Poniższe makro działało dla mnie bezbłędnie (również bez parametrów!) W Visual C ++ Express 2010:

#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,N,...) N
#define VA_NUM_ARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
#define VA_NARGS(...)  bool(#__VA_ARGS__) ? (VA_NUM_ARGS_IMPL_((__VA_ARGS__, 24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))) : 0

Jeśli chcesz mieć makro z opcjonalnymi parametrami, możesz zrobić:

//macro selection(vc++)
#define SELMACRO_IMPL(_1,_2,_3, N,...) N
#define SELMACRO_IMPL_(tuple) SELMACRO_IMPL tuple
#define mymacro1(var1) var1
#define mymacro2(var1,var2) var2*var1
#define mymacro3(var1,var2,var3) var1*var2*var3
#define mymacro(...) SELMACRO_IMPL_((__VA_ARGS__, mymacro3(__VA_ARGS__), mymacro2(__VA_ARGS__), mymacro1(__VA_ARGS__))) 

To zadziałało również dla mnie w vc. Ale to nie działa dla zerowych parametrów.

int x=99;
x=mymacro(2);//2
x=mymacro(2,2);//4
x=mymacro(2,2,2);//8

Dostajęunresolved external symbol _bool referenced in function _main
— Avidan Borisov

tak, może się to zdarzyć w niektórych przypadkach. musisz wiedzieć, że bool (#__ VA_ARGS__)? różni się od innych makr, ponieważ jest oceniany w czasie wykonywania. w zależności od przypadku możesz jednak pominąć tę część kodu.
— Syphorlate

2
Skończyło się na pastebin.com/H3T75dcn, który działa doskonale (również 0 argumentów).
— Avidan Borisov

Dzięki za link i tak, możesz to zrobić również za pomocą sizeof, ale dla mnie to nie działało w niektórych przypadkach, ale zasada jest taka sama (ocena logiczna).
— Syphorlate

Czy mógłbyś podać kilka przykładów, w których się to nie udaje?
— Avidan Borisov

7

gcc/ g++obsługuje makra varargs, ale nie sądzę, aby to było standardowe, więc używaj go na własne ryzyko.


4
Są standardowe w C99 i są również dodawane do C ++ 0x.
— greyfade

5
#include <stdio.h>

#define PP_NARG(...) \
    PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
    PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
    _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ 
    _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
    _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
    _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
    _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
    _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
    _61,_62,_63,N,...) N
#define PP_RSEQ_N() \
    63,62,61,60,                   \
    59,58,57,56,55,54,53,52,51,50, \
    49,48,47,46,45,44,43,42,41,40, \
    39,38,37,36,35,34,33,32,31,30, \
    29,28,27,26,25,24,23,22,21,20, \
    19,18,17,16,15,14,13,12,11,10, \
    9,8,7,6,5,4,3,2,1,0

#define PP_CONCAT(a,b) PP_CONCAT_(a,b)
#define PP_CONCAT_(a,b) a ## b

#define THINK(...) PP_CONCAT(THINK_, PP_NARG(__VA_ARGS__))(__VA_ARGS__)
#define THINK_0() THINK_1("sector zz9 plural z alpha")
#define THINK_1(location) THINK_2(location, 42)
#define THINK_2(location,answer) THINK_3(location, answer, "deep thought")
#define THINK_3(location,answer,computer) \
  printf ("The answer is %d. This was calculated by %s, and a computer to figure out what this"
          " actually means will be build in %s\n", (answer), (computer), (location))

int
main (int argc, char *argv[])
{
  THINK (); /* On compilers other than GCC you have to call with least one non-default argument */
}

ZASTRZEŻENIE: W większości nieszkodliwe.


w twoim kodzie jest błąd. proszę :%s/MY_MACRO_/THINK_/g:)
— João Portela

również nie działało z zerowymi argumentami przy użyciu g ++i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
— João Portela.

1
Zero argumentów nie istnieje dla makr variadiac, ponieważ pusty token jest prawidłowym symbolem zastępczym.
— Paul Fultz II,

3

Nie do tego jest przeznaczony preprocesor.

To powiedziawszy, jeśli chcesz wejść w obszar poważnie trudnego programowania makr z odrobiną czytelności, powinieneś przyjrzeć się bibliotece preprocesorów Boost . W końcu nie byłoby C ++, gdyby nie było trzech poziomów programowania całkowicie zgodnych z Turingiem (preprocesor, metaprogramowanie szablonów i podstawowy poziom C ++)!


3
#define MY_MACRO_3(X,Y,Z) ...
#define MY_MACRO_2(X,Y) MY_MACRO(X,Y,5)
#define MY_MACRO_1(X) MY_MACRO(X,42,5)

W momencie wywołania wiesz, ile argumentów przekażesz, więc naprawdę nie ma potrzeby przeciążania.


2
Właściwie to pytałem o istnienie tej funkcji.
— Cenoc

3

Bardziej zwięzła wersja kodu Dereka Ledbettera:

enum
{
    plain = 0,
    bold = 1,
    italic = 2
};


void PrintString(const char* message = NULL, int size = 0, int style = 0)
{
}


#define PRINT_STRING(...) PrintString(__VA_ARGS__)


int main(int argc, char * const argv[])
{ 
    PRINT_STRING("Hello, World!");
    PRINT_STRING("Hello, World!", 18);
    PRINT_STRING("Hello, World!", 18, bold);

    return 0;
}

3

Jako wielki fan okropnych potworów makro, chciałem rozwinąć odpowiedź Jasona Denga i uczynić ją użyteczną. (Na lepsze lub gorsze.) Oryginał nie jest zbyt przyjemny w użyciu, ponieważ za każdym razem, gdy chcesz zrobić nowe makro, musisz modyfikować zupę z dużym alfabetem, a jest jeszcze gorzej, jeśli potrzebujesz innej ilości argumentów.

Zrobiłem więc wersję z następującymi funkcjami:

  • 0 argumentów działa
  • Od 1 do 16 argumentów bez żadnych modyfikacji w niechlujnej części
  • Łatwo napisać więcej funkcji makr
  • Testowane w GCC 10, Clang 9, Visual Studio 2017

Obecnie zrobiłem maksymalnie 16 argumentów, ale jeśli potrzebujesz więcej (naprawdę teraz? Po prostu robisz się głupi ...) możesz edytować FUNC_CHOOSER i CHOOSE_FROM_ARG_COUNT, a następnie dodać przecinki do NO_ARG_EXPANDER.

Zobacz doskonałą odpowiedź Jasona Denga, aby uzyskać więcej informacji na temat implementacji, ale wstawię kod tutaj:

#include <stdio.h>

void realCreate(int x, int y)
{
    printf("(%d, %d)\n", x, y);
}

// This part you put in some library header:
#define FUNC_CHOOSER(_f0, _f1, _f2, _f3, _f4, _f5, _f6, _f7, _f8, _f9, _f10, _f11, _f12, _f13, _f14, _f15, _f16, ...) _f16
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(F, ...) FUNC_RECOMPOSER((__VA_ARGS__, \
            F##_16, F##_15, F##_14, F##_13, F##_12, F##_11, F##_10, F##_9, F##_8,\
            F##_7, F##_6, F##_5, F##_4, F##_3, F##_2, F##_1, ))
#define NO_ARG_EXPANDER(FUNC) ,,,,,,,,,,,,,,,,FUNC ## _0
#define MACRO_CHOOSER(FUNC, ...) CHOOSE_FROM_ARG_COUNT(FUNC, NO_ARG_EXPANDER __VA_ARGS__ (FUNC))
#define MULTI_MACRO(FUNC, ...) MACRO_CHOOSER(FUNC, __VA_ARGS__)(__VA_ARGS__)

// When you need to make a macro with default arguments, use this:
#define create(...) MULTI_MACRO(CREATE, __VA_ARGS__)
#define CREATE_0() CREATE_1(0)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_2(x, y) \
    do { \
        /* put whatever code you want in the last macro */ \
        realCreate(x, y); \
    } while(0)


int main()
{
    create();
    create(10);
    create(20, 20);
    //create(30, 30, 30);  // Compilation error
    return 0;
}

2

Możesz użyć BOOST_PP_OVERLOADzboost biblioteki.

Przykład z oficjalnego dokumentu doładowania :

#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/arithmetic/add.hpp>

#define MACRO_1(number) MACRO_2(number,10)
#define MACRO_2(number1,number2) BOOST_PP_ADD(number1,number2)

#if !BOOST_PP_VARIADICS_MSVC

#define MACRO_ADD_NUMBERS(...) BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__)

#else

// or for Visual C++

#define MACRO_ADD_NUMBERS(...) \
  BOOST_PP_CAT(BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())

#endif

MACRO_ADD_NUMBERS(5) // output is 15
MACRO_ADD_NUMBERS(3,6) // output is 9

0

W zależności od tego, czego potrzebujesz, możesz to zrobić za pomocą argumentów var z makrami. Otóż, opcjonalne parametry czy przeciążenie makr, nie ma czegoś takiego.


-1

Żaden z powyższych przykładów (od Dereka Ledbettera, Davida Sorkovsky'ego i Joe D) do liczenia argumentów z makrami nie działał dla mnie przy użyciu Microsoft VCC 10. __VA_ARGS__Argument jest zawsze traktowany jako pojedynczy argument (tokenizowanie go z ##lub nie), więc przesunięcie argumentów, na którym polegają te przykłady, nie działa.

Krótka odpowiedź, jak twierdzi wielu innych powyżej: nie, nie można przeciążać makr ani używać w nich opcjonalnych argumentów.


1
Możesz, ale tylko w C99 lub C ++ 11 (ze względu na posiadanie __VA_ARGS__). VC2010 to C89 / C ++ 03 (z niektórymi bitami C ++ 11 zaczynają się pojawiać, ale jeszcze nie ten).
— puetzk
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.