Opcjonalne parametry z makrami C++

Czy Jest jakiś sposób na uzyskanie opcjonalnych parametrów za pomocą makr C++? Jakieś przeciążenie też byłoby miłe.

 81

13 answers

Jest na to jeden sposób. Używa listy argumentów dwa razy, najpierw tworząc nazwę pomocniczego makra, a następnie przekazując argumenty do tego pomocniczego makra. Używa standardowej sztuczki, aby zliczyć 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;
}

To ułatwia rozmówcy makra, ale nie pisarzowi.

 125
Author: Derek Ledbetter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 19:28:17

Z wielkim szacunkiem dla Dereka Ledbettera za jego odpowiedź - i z przeprosinami za ożywienie starego pytania.

Zrozumienie tego, co robi, i poznanie gdzie indziej możliwości preceed the __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 mogą do końca zobaczyć, jak to działa, przejdę przez rzeczywiste przetwarzanie, zaczynając od następujących kod...

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

Który staje się tylko 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 [ie: jeśli Opcja bez argumentu nie jest dozwolona].

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

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

 59
Author: David Sorkovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-28 13:42:46

Makra C++ nie zmieniły się od C. Ponieważ C nie miało przeciążeń i domyślnych argumentów dla funkcji, z pewnością nie miało ich dla makr. Aby odpowiedzieć na twoje pytanie: nie, te funkcje nie istnieją dla makr. Jedyną opcją jest zdefiniowanie wielu makr o różnych nazwach (lub w ogóle nie używaj makr).

Jako wskazówka: w C++ dobrą praktyką jest odejście od makr w jak największym stopniu. Jeśli potrzebujesz takich funkcji, istnieje duża szansa, że nadużywanie makr.

 31
Author: sepp2k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:09:22

Z największym szacunkiem dla Dereka Ledbettera, David Sorkovsky, Syphorlate dla ich odpowiedzi, wraz z pomysłową metodą wykrywania pustych argumentów makro przez Jens Gustedt W

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

W końcu wychodzę z czymś, co zawiera wszystkie sztuczki, tak, że rozwiązanie

  1. używa tylko standardowych makr C99 do pozwala to osiągnąć przeciążenie funkcji, bez rozszerzenia GCC/clang/MSVC (np. Połykanie przecinków przez specyficzne wyrażenie , ##__VA_ARGS__ dla GCC / CLANG i połykanie niejawne przez ##__VA_ARGS__ dla MSVC). Więc nie krępuj się przekazać brakujące --std=c99 do kompilatora, jeśli chcesz=)
  2. Działa dla zerowego argumentu , a także nieograniczonej liczby argumentów , jeśli rozszerzysz go dalej zgodnie z własnymi potrzebami
  3. Działa rozsądnie cross-platform , przynajmniej sprawdzone dla

    • 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 na OS X 10.10.1)
    • W 2013 roku, po raz pierwszy w Polsce pojawiła się Wersja 4.0.1.2014, która została wydana w 2014 roku.]}

Dla leniwych, po prostu przejdź do ostatniego postu, aby skopiować źródło. Poniżej znajduje się szczegółowy Wyjaśnienie, które mam nadzieję pomaga i inspiruje wszystkich ludzi szukających ogólnych rozwiązań, takich jak ja. =)

Oto jak to będzie. Najpierw zdefiniuj widoczną przez użytkownika przeciążoną "funkcję", nazwałem ją create i powiązaną rzeczywistą definicję funkcji realCreate, a także definicje makr 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)

Część MACRO_CHOOSER(__VA_ARGS__) ostatecznie ustępuje nazwom definicji makr, a druga część (__VA_ARGS__) zawiera ich listy parametrów. Tak więc wywołanie użytkownika do create(10) rozwiązuje się na CREATE_1(10), część CREATE_1 pochodzi z MACRO_CHOOSER(__VA_ARGS__), a część (10) pochodzi z drugiej (__VA_ARGS__).

The MACRO_CHOOSER używa sztuczki, że jeśli __VA_ARGS__ jest pusty, następujące wyrażenie jest konkatenowane do prawidłowego wywołania makra przez preprocesor:

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

Po prostu możemy zdefiniować to wywołanie makra jako

#define NO_ARG_EXPANDER() ,,CREATE_0

Zwróć uwagę na dwa przecinki, są one wyjaśnione wkrótce. Kolejnym użytecznym makrem jest

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

Więc rozmowy z

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, mamy policzyć liczbę argumentów później. Oto kolejna sztuczka: preprocesor wykonuje tylko prostą wymianę tekstu. Wnioskuje liczbę argumentów wywołania makro tylko z liczby przecinków, które widzi w nawiasach. Rzeczywiste "argumenty" oddzielone przecinkami nie muszą mieć poprawnej składni. Mogą być dowolnym tekstem. Oznacza to, że w powyższym przykładzie NO_ARG_EXPANDER 10 () jest liczony jako 1 argument na środkowy telefon. NO_ARG_EXPANDER 20 i 20 () są liczone jako 2 argumenty dla dolnego wywołania.

Jeśli użyjemy następujących makr pomocniczych do dalszego ich rozwinięcia

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

Końcowe , Po CREATE_1 jest obejściem dla GCC/CLANG, tłumiąc (fałszywie dodatni) błąd mówiący, że ISO C99 requires rest arguments to be used podczas przekazywania -pedantic do kompilatora. FUNC_RECOMPOSER jest obejściem dla MSVC, lub nie może poprawnie zliczyć liczby argumentów (np. przecinków) w nawiasach wywołań makr. Na wyniki są dalej rozstrzygane 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 widać, ostatnim krokiem, którego potrzebujemy, jest użycie standardowej sztuczki z liczeniem argumentów, aby w końcu wybrać żądane nazwy wersji makr: {]}

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

Który rozwiązuje wyniki do

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

I z pewnością daje nam pożądane, rzeczywiste wywołania funkcji:

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

Składając wszystko razem, z pewnymi przegrupowaniami stwierdzeń dla lepszej czytelności, całe źródło 2-argumentu przykład 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;
}

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

Jeśli istnieje jakiekolwiek dalsze uproszczenie tego podejścia, proszę dać mi znać na

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

Znowu specjalne podziękowania dla wszystkich wspaniałych ludzi, którzy zainspirowali i doprowadzili mnie do osiągnięcia tego dzieła! =)

 20
Author: Jason Deng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-23 07:03:26

Dla każdego, kto boleśnie szuka jakiegoś rozwiązania VA_NARGS, które współpracuje z Visual C++. Kolejne makro działało mi bez zarzutu (również z zerowymi parametrami!) 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 to 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 dla mnie również 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
 9
Author: Syphorlate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-24 01:33:58

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

 6
Author: Paul R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:08:32
#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.

 5
Author: Joe D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-07 23:11:26

Do tego nie jest przeznaczony preprocesor.

To powiedziawszy, jeśli chcesz wejść w obszar poważnie trudnego programowania makr z odrobiną czytelności, powinieneś rzucić okiem na bibliotekę preprocesora Boost. W końcu nie byłoby to C++, gdyby nie istniały trzy całkowicie kompatybilne z Turingiem poziomy programowania (preprocesor, metaprogramowanie szablonów i poziom podstawowy C++)!

 3
Author: Pontus Gagge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:12:05
#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)

Wiesz w momencie wywołania, ile argów podasz, więc naprawdę nie ma potrzeby przeładowywania.

 3
Author: Crazy Eddie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:14:58

W zależności od tego, czego potrzebujesz, możesz to zrobić za pomocą var args z makrami. Teraz, opcjonalne parametry lub przeciążenie makra, nie ma czegoś takiego.

 1
Author: Gianni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:10:43

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;
}
 1
Author: Megamozg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-02 04:31:09

Możesz użyć BOOST_PP_OVERLOAD z boost biblioteki.

Przykład z official boost doc :

#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
 1
Author: disable13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-22 09:16:48

Żaden z powyższych przykładów (od Dereka Ledbettera, Davida Sorkovsky ' ego i Joe D) liczenia argumentów za pomocą makr nie zadziałał dla mnie przy użyciu Microsoft VCC 10. Argument __VA_ARGS__ jest zawsze uważany za pojedynczy argument (oznaczający go symbolem ## lub nie), więc argument, w którym te przykłady się opierają, nie działa.

Tak więc, krótka odpowiedź, jak twierdzi wiele innych powyżej: Nie, Nie można przeciążać makr ani używać na nich opcjonalnych argumentów.

 -1
Author: TheProgammerd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-28 16:16:09