Необязательные параметры с макросами С++

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

Ответ 1

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

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

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

Ответ 2

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

Получение понимания того, что он делал, и понимание в других местах возможности предшествовать __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: я не эксперт, поэтому я очень рад слышать комментарии (хорошие, плохие или другие)!

Ответ 3

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

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

Ответ 4

С большим уважением к Дереку Ледбеттеру, Дэвиду Сорковскому, Syphorlate для их ответов, вместе с гениальным методом для обнаружения пустых макро-аргументов Йенса Густедта в

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

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

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

    • 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 4 в Windows 7 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()

Ingeniusly, мы можем определить этот полученный макрокоманд как

#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 () считается одним аргументом для среднего вызова. NO_ARG_EXPANDER 20 и 20 () считаются двумя аргументами для нижнего вызова соответственно.

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

##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/С++ для нас, сумасшедших. Использование перегруженных API-интерфейсов становится очень приятным и приятным. =)

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

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

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

Ответ 5

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

Если вам нужен макрос с дополнительными параметрами, вы можете сделать:

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

Это работало и для меня, и для vc. Но он не работает для нулевых параметров.

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

Вы можете использовать BOOST_PP_OVERLOAD из библиотеки boost.

Пример из официального документа Boost:

#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

Ответ 12

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

Ответ 13

Как большой поклонник ужасных макро-монстров, я хотел бы расширить ответ Джейсона Денга и сделать его действительно полезным. (Хорошо это или плохо.) Оригинал не очень хорош в использовании, потому что вам нужно модифицировать суп из большого алфавита каждый раз, когда вы хотите создать новый макрос, и это еще хуже, если вам нужно различное количество аргументов.

Поэтому я сделал версию с этими функциями:

  • Работает 0 аргументов
  • От 1 до 16 аргументов без каких-либо изменений в грязной части
  • Легко написать больше макро функций
  • Протестировано в gcc 10, clang 9, Visual Studio 2017

В настоящее время я только что установил максимум 16 аргументов, но если вам нужно больше (правда, теперь вы просто глупите...), вы можете отредактировать FUNC_CHOOSER и CHOOSE_FROM_ARG_COUNT, а затем добавить несколько запятых в NO_ARG_EXPANDER.

Пожалуйста, посмотрите превосходный ответ Джейсона Денга для более подробной информации о реализации, но я просто разместлю код здесь:

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

Ответ 14

Ни один из приведенных выше примеров (от Дерека Ледбеттера, Дэвида Сорковского и Джо D) для подсчета аргументов с макросами работал у меня с использованием Microsoft VCC 10. Аргумент __VA_ARGS__ всегда рассматривается как один аргумент (токен-izing it с ## или нет), поэтому смещение аргументов, в котором эти примеры не работают, не работает.

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