Необязательные параметры с макросами C ++ - PullRequest
94 голосов
/ 15 июня 2010

Есть ли способ получить необязательные параметры с помощью макросов C ++?Какая-то перегрузка тоже подойдет.

Ответы [ 14 ]

135 голосов
/ 15 июня 2010

Вот один из способов сделать это.Он использует список аргументов дважды, сначала для формирования имени вспомогательного макроса, а затем для передачи аргументов этому вспомогательному макросу.Он использует стандартный трюк для подсчета количества аргументов макроса.

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

Это облегчает вызов макроса, но не автора.

73 голосов
/ 11 января 2012

С большим уважением к Дереку Ледбеттеру за его ответ - и с извинениями за возрождение старого вопроса.

Понимание того, что он делал, и понимание в другом месте способности предшествовать __VA_ARGS__ с ## позволило мне придумать вариант ...

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

Для таких экспертов, как я, которые сталкиваются с ответом, но не могут понять, как он работает, я перейду к реальной обработке, начиная со следующего кода ...

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 

становится ...

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

Что становится лишь шестым аргументом ...

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

PS: удалить #define для XXX_0, чтобы получить ошибку компиляции [то есть: если опция без аргументов не разрешена].

PPS: Было бы неплохо иметь недопустимые ситуации (например, 5), которые дают программисту более ясную ошибку компиляции!

PPPS: я не эксперт, поэтому я очень рад слышать комментарии (хорошие, плохие или другие)!

30 голосов
/ 15 июня 2010

Макросы C ++ не изменились по сравнению с C. Поскольку в C не было перегрузки и аргументов по умолчанию для функций, у него, разумеется, не было их для макросов. Итак, чтобы ответить на ваш вопрос: нет, эти функции не существуют для макросов. Единственный вариант - определить несколько макросов с разными именами (или вообще не использовать макросы).

Как примечание: в C ++ считается хорошей практикой как можно больше уходить от макросов. Если вам нужны такие функции, есть вероятность, что вы злоупотребляете макросами.

22 голосов
/ 21 января 2015

С большим уважением к Дерек Ледбеттер , Дэвид Сорковский , Syphorlate для их ответов, вместе с оригинальным методом обнаружения пустых макро-аргументов с помощью Jens Gustedt at

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

наконец я придумываю что-то, что включает в себя все приемы, так что решение

  1. использует только стандартные макросы C99 для достижения перегрузки функций, без расширения GCC / CLANG / MSVC (т. е. глотание запятой определенным выражением , ##__VA_ARGS__ для GCC / CLANG и неявное глотание на ##__VA_ARGS__ для MSVC).Так что не стесняйтесь передавать недостающие --std=c99 вашему компилятору, если хотите =)
  2. Работает для нулевой аргумент , а также неограниченное количество аргументов , еслиВы расширяете его в соответствии с вашими потребностями
  3. Работает разумно кросс-платформенный , по крайней мере проверено на

    • GNU / Linux + GCC (GCC 4.9.2 в CentOS 7.0 x86_64)
    • GNU / Linux + CLANG / LLVM , (CLANG / LLVM 3.5.0 в CentOS 7.0 x86_64)
    • OS X + Xcode , (XCode 6.1.1 в OS X Yosemite 10.10.1)
    • Windows + Visual Studio , (Visual Studio 2013 Update 4 в Windows7 SP1 64 бита)

Для ленивцев просто перейдите к самому последнему посту, чтобы скопировать источник.Ниже приводится подробное объяснение, которое, мы надеемся, помогает и вдохновляет всех людей, которые ищут общие решения __VA_ARGS__, такие как я.=)

Вот как это происходит.Сначала определите видимую пользователем перегруженную «функцию», я назвал ее create и соответствующее фактическое определение функции realCreate, а также определения макросов с различным количеством аргументов CREATE_2, CREATE_1, CREATE_0, какпоказано ниже:

#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__) в конечном итоге разрешает имена определений макросов, а вторая часть (__VA_ARGS__) содержит их списки параметров.Таким образом, пользовательский вызов create(10) преобразуется в CREATE_1(10), часть CREATE_1 поступает из MACRO_CHOOSER(__VA_ARGS__), а часть (10) поступает из второй (__VA_ARGS__).

Используется MACRO_CHOOSERХитрость в том, что если __VA_ARGS__ пусто, следующее выражение объединяется препроцессором в допустимый вызов макроса:

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

Изобретательно, мы можем определить этот результирующий вызов макроса как

#define NO_ARG_EXPANDER() ,,CREATE_0

Обратите внимание на две запятые, они скоро будут объяснены.Следующий полезный макрос -

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

, поэтому вызовы

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

фактически расширены до

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

Как следует из названия макроса, мы должны считатьколичество аргументов позже.Вот еще одна хитрость: препроцессор выполняет только простую замену текста.Он выводит количество аргументов вызова макроса просто из числа запятых, которые он видит в скобках.Фактические «аргументы», разделенные запятыми, не обязательно должны иметь правильный синтаксис.Они могут быть любым текстом.То есть, в приведенном выше примере NO_ARG_EXPANDER 10 () считается как 1 аргумент для среднего вызова.NO_ARG_EXPANDER 20 и 20 () считаются 2 аргументами для нижнего вызова соответственно.

Если мы используем следующие вспомогательные макросы для их дальнейшего расширения

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

Трейлинг , послеCREATE_1 - это обходной путь для GCC / CLANG, подавляющий ошибку (ложное срабатывание), говорящую, что ISO C99 requires rest arguments to be used при передаче -pedantic вашему компилятору.FUNC_RECOMPOSER - это обходной путь для MSVC, или он не может правильно подсчитать количество аргументов (то есть запятых) в скобках макросов.Далее результаты сводятся к

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

Как вы, возможно, уже видели, как орлиные глаза, последний единственный шаг, который нам нужен, это использование стандартного трюка для подсчета аргументов, чтобы наконец выбрать нужные имена версий макросов:

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

, который разрешает результаты в

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

и, конечно, дает нам нужные, реальные вызовы функций:

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

Собирает все вместе, с некоторой перестановкой операторовдля лучшей читабельности, весь источник примера с двумя аргументами находится здесь:

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

Несмотря на сложность, уродливость, отягощающую разработчиков API, для сумасшедших людей существует решение для перегрузки и установки необязательных параметров функций C / C ++. Использование перегруженных API-интерфейсов становится очень приятным и приятным. =) * * 1 123

Если есть еще возможное упрощение этого подхода, пожалуйста, дайте мне знать на

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

Еще раз отдельное спасибо всем замечательным людям, которые вдохновили и привели меня к выполнению этой работы! =)

9 голосов
/ 08 сентября 2012

Для тех, кто мучительно ищет какое-то решение VA_NARGS, которое работает с Visual C ++.Следующий макрос работал для меня безупречно (также с нулевыми параметрами!) В 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

Если вы хотите макрос с дополнительными параметрами, вы можете сделать:работал для меня также в VC.Но это не работает для нулевых параметров.

int x=99;
x=mymacro(2);//2
x=mymacro(2,2);//4
x=mymacro(2,2,2);//8
6 голосов
/ 15 июня 2010

gcc / g++ поддерживает макросы varargs но я не считаю это стандартным, поэтому используйте его на свой страх и риск.

5 голосов
/ 24 августа 2010
#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 */
}

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: В основном Безвредны.

3 голосов
/ 15 июня 2010
#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)

Вы знаете, в точке вызова, сколько аргументов вы собираетесь передать, так что перегрузка действительно не нужна.

3 голосов
/ 15 июня 2010

Это не совсем то, для чего предназначен препроцессор.

Тем не менее, если вы хотите войти в область серьезного макропрограммирования с минимальной читабельностью, вам следует взглянуть на библиотеку препроцессора Boost .В конце концов, это был бы не C ++, если бы не было трех полностью совместимых с Turing уровней программирования (препроцессор, метапрограммирование шаблонов и базовый уровень C ++)!

2 голосов
/ 02 октября 2014

Более краткая версия кода Дерека Ледбеттера:

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...