Можем ли мы реализовать макрос max или min, который может принимать переменные аргументы (более двух параметров)

Я хочу реализовать новый макрос max/min, который может принимать более двух параметров, например:

#define max( ... ) ...

а затем я могу использовать его так:

max( p0, p1, p2, p3 )
max( 2, 4, 100 )
max( 1,2,3,4,5,6,7 ) -> 7

может ли этот макрос помочь нам реализовать этот макрос?

#define PP_EXPAND(X) X
#define PP_ARG_COUNT(...) PP_EXPAND(PP_ARG_POPER(__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
#define PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N

#define PP_ARG_AT(Index, ...) PP_ARG_AT_##Index(__VA_ARGS__)
#define PP_ARG_AT_0(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, __VA_ARGS__))
#define PP_ARG_AT_1(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, __VA_ARGS__))
#define PP_ARG_AT_2(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, __VA_ARGS__))
#define PP_ARG_AT_3(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, __VA_ARGS__))
#define PP_ARG_AT_4(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, __VA_ARGS__))
#define PP_ARG_AT_5(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, __VA_ARGS__))
#define PP_ARG_AT_6(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __VA_ARGS__))
#define PP_ARG_AT_7(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, __VA_ARGS__))
#define PP_ARG_AT_8(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, _8, __VA_ARGS__))
#define PP_ARG_AT_9(...)  PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, _7, __VA_ARGS__))
#define PP_ARG_AT_10(...) PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, _6, __VA_ARGS__))
#define PP_ARG_AT_11(...) PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, _5, __VA_ARGS__))
#define PP_ARG_AT_12(...) PP_EXPAND(PP_ARG_POPER(_1, _2, _3, _4, __VA_ARGS__))
#define PP_ARG_AT_13(...) PP_EXPAND(PP_ARG_POPER(_1, _2, _3, __VA_ARGS__))
#define PP_ARG_AT_14(...) PP_EXPAND(PP_ARG_POPER(_1, _2, __VA_ARGS__))
#define PP_ARG_AT_15(...) PP_EXPAND(PP_ARG_POPER(_1, __VA_ARGS__))
#define PP_ARG_AT_16(...) PP_EXPAND(PP_ARG_POPER( __VA_ARGS__))

person Hikari    schedule 01.04.2014    source источник
comment
привет, я не буду использовать этот код, я задал этот вопрос, потому что я хочу изучить эту технику препроцессора в соответствии с этим заголовком, большое спасибо   -  person Hikari    schedule 01.04.2014
comment
Это С или С++? Выбери один. В C++ вместо макросов можно использовать более надежные методы.   -  person user253751    schedule 01.04.2014


Ответы (4)


В C++11 std::max работает с initializer_list, поэтому вы можете использовать

std::max({40, 31, 42, 13, 4, 25, 16, 27});

И если вам действительно нужен синтаксис MAX(p1, p2, p3), вы можете сделать:

#define MAX(...) std::max({__VA_ARGS__})
person Jarod42    schedule 10.07.2014

Существует алгоритм С++ STL, чтобы сделать то же самое:

max_element.

min_element

Начинает использовать их вместо написания макроса для достижения этой цели:

 int arr[] = {1,2,3,4,5};
 int* min = std::min_element(arr, arr+5);
 int* max = std::max_element(arr,arr+5);
 std::cout<<"min:"<<*min<<"max:"<<*max<<std::endl;
person Mantosh Kumar    schedule 01.04.2014
comment
привет, я не буду использовать этот код, я задал этот вопрос, потому что я хочу изучить эту технику препроцессора, большое спасибо... - person Hikari; 01.04.2014
comment
@Hikari: почти нет необходимости писать MACRO на чистом C++. Этот алгоритм STL будет таким же эффективным (почти), как MACRO. Вы также можете использовать встроенную концепцию для достижения поведения типа MACRO. Поэтому вам следует изучить эти концепции вместо МАКРО. Выбор за вами :) - person Mantosh Kumar; 01.04.2014
comment
@tmp Это все еще может быть интересным учебным упражнением. Мы не можем читать мысли Хикари. - person user253751; 01.04.2014

Используя Boost.Preprocessor, вы можете реализовать его так:

#define MAX_FOLD(s, state, elem) BOOST_PP_MAX(state, elem) 
#define MAX(...) BOOST_PP_SEQ_FOLD_LEFT(MAX_FOLD, 0, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) 

Поскольку препроцессор напрямую не поддерживает сравнение во время расширения, реализация всего этого с нуля требует большой работы. Используя методы здесь, вы можете реализовать счетчик и конструкция цикла while. При этом вы можете реализовать вычитание, что позволит вам реализовать меньше (или больше), что необходимо для MAX. Затем с помощью другого while вы можете сложить аргументы varidiac.

Наконец, есть некоторые ограничения на выполнение всего этого в препроцессоре. Препроцессор не полностью завершен по Тьюрингу. Таким образом, в примере с повышением вы будете ограничены значениями от 0 до 256 (это полностью ограничение повышения, если вы сделаете это самостоятельно, вы можете увеличить это ограничение). В зависимости от того, чего вы хотите достичь, может быть лучше написать вариативную функцию для max:

template<class T, class U>
T max(T x, T y)
{
    return x > y ? x : y;
}

template<class... Ts>
T max(T x, Ts... xs)
{
    return max(x, max(xs...)); 
}
person Paul Fultz II    schedule 04.04.2014

Ваш вопрос содержит половину ответа - вы можете построить макросы min/max с переменным количеством аргументов, используя технику, показанную в макросе PP_ARG_COUNT (как и в исходном коде, количество аргументов будет иметь ограничение, но вы можете его выбрать).

Вот пример кода (до 4 аргументов):

#include <stdio.h>

#define __START(op, A, B, C, D, N, ...) __ARGS_##N(op, A, B, C, D)
#define __ARGS_1(op, A, B, C, D) A
#define __ARGS_2(op, A, B, C, D) __##op(A, B)
#define __ARGS_3(op, A, B, C, D) __##op(A, __##op(B, C))
#define __ARGS_4(op, A, B, C, D) __##op(__##op(A, B), __##op(C, D))


#define __MIN(A, B) ((A) < (B) ? (A) : (B))
#define __MAX(A, B) ((A) > (B) ? (A) : (B))

#define min(...) __START(MIN, __VA_ARGS__, 4, 3, 2, 1)
#define max(...) __START(MAX, __VA_ARGS__, 4, 3, 2, 1)

int main(void)
{
   printf("min(1) -> %d\n\n", min(1));
   printf("min(1.5, 2) -> %lf\n", min(1.5,2));
   printf("min(3, 2, 1.5) -> %lf\n", min(3,2,1.5));
   printf("min(1, 2, 3, 4) -> %d\n", min(1,2,3,4));
   printf("min(2, 3, 4, 1) -> %d\n\n", min(2,3,4,1));

   printf("max(2.5, 2.0) -> %lf\n", max(2.5, 2.0));
   printf("max(3, 2, 3.5) -> %lf\n", max(3, 2, 3.5));
   printf("max(1, 2, 3, 4) -> %d\n", max(1, 2, 3, 4));
   printf("max(2, 3, 4, 1) -> %d\n", max(2, 3, 4, 1));

   return 0;
}

Если вы скомпилируете и запустите программу, вы получите следующий вывод:

min(1) -> 1

min(1.5, 2) -> 1.500000
min(3, 2, 1.5) -> 1.500000
min(1, 2, 3, 4) -> 1
min(2, 3, 4, 1) -> 1

max(2.5, 2.0) -> 2.500000
max(3, 2, 3.5) -> 3.500000
max(1, 2, 3, 4) -> 4
max(2, 3, 4, 1) -> 4

Как это работает. Макрос __START принимает следующие аргументы:

  • op - имя макроса (без предшествующего двойного подчеркивания), который выполняет необходимую операцию всего с двумя аргументами
  • A, B, C, D - эти аргументы получат аргументы min/max и, возможно, некоторые фиктивные, если количество аргументов min/max было меньше максимального (4 в этом примере кода).
  • N - количество min/max аргументов.
  • ... - некоторые другие фиктивные аргументы.

__START расширится до одного из __ARGS_1..__ARGS_4 в зависимости от количества аргументов. Количество аргументов получается путем добавления 4, 3, 2, 1 аргументов, вызывающих макрос __START:

#define min(...) __START(MIN, __VA_ARGS__, 4, 3, 2, 1)

Итак, если вы вызовете, например, min(1.5, 2.5, 3.5), он расширится до (я добавил имена аргументов в комментарии):

__START(/*op=*/ MIN, /*A=*/ 1.5, /*B=*/ 2.5, /*C=*/ 3.5, /*D=*/ 4, /*N=*/ 3, 2, 1)

Затем __START расширится до __ARGS_3, и следующие расширения тривиальны. Теперь хорошо видно, как "считается" количество аргументов и как это работает. Вы можете легко реализовать те же макросы с другим функционалом и увеличить максимальное количество аргументов, например sum:

#define __SUM(A, B) ((A)+(B))
#define sum(...) __START(SUM, __VA_ARGS__, 4, 3, 2, 1)

Думал, что это не так полезно, как min/max.

P.S. Если вы используете Visual C++, вам нужно будет добавить обходной путь (например, PP_EXPAND в первом сообщении), чтобы преодолеть ошибку препроцессора VC++, подробности см.. Я использовал GCC, он не нужен.

person Oleg Skydan    schedule 23.07.2019