Can we implement a max or min macro, which can take variable arguments (more than two parameters )
Asked Answered
U

4

5

I want to implement a new max/min macro, which can take more than two parameter, for example:

#define max( ... ) ...

and then, I can use it like this:

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

if this macro can help us to implement that macro?

#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__))
Underdone answered 1/4, 2014 at 1:42 Comment(3)
hi, i will not use this code, i asked this question, because i want to study this technique of preprocessor accoss this title, thanks a lotUnderdone
Is this C or C++? Pick one. In C++ there are more robust techniques you can use instead of macros.Slump
boost.org/doc/libs/1_55_0/libs/preprocessor/doc/ref/max_d.htmlMonarchal
Q
6

In C++11, std::max works with initializer_list, so you may use

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

And if you really want MAX(p1, p2, p3) syntax, you may do:

#define MAX(...) std::max({__VA_ARGS__})
Quotable answered 10/7, 2014 at 16:43 Comment(0)
D
3

Your question contains a half of the answer - you can build min/max macros with the variable arguments number using the technique shown in PP_ARG_COUNT macro (as with original code the number of arguments will have the limit, but you can choose it).

Here is the sample code (up to 4 arguments):

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

If you compile and run the program you will get the following output:

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

How it works. The __START macro takes the following arguments:

  • op - the macro name (without preceding double underscore) which does the necessary operation with just two arguments
  • A, B, C, D - these arguments will receive the arguments of the min/max and possibly some dummy ones if the number of min/max arguments was less then the maximum (4 in this sample code).
  • N - the number of min/max arguments.
  • ... - some other dummy arguments.

__START will expand to one of the __ARGS_1..__ARGS_4 depending of arguments number. The arguments number is obtained by adding 4, 3, 2, 1 arguments invoking __START macro:

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

So, if you invoke for instance min(1.5, 2.5, 3.5) it will expand to (I added arguments names in comments):

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

The __START will then expand to __ARGS_3 and the following expansions are trivial. Now it is clearly seen how arguments number "counted" and how it works. You can easily implement the same macros with other functionality and increase maximum number of arguments, for example sum:

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

Thought it is not as useful as min/max ones.

P.S. If you use Visual C++ you will need to add some workaround (like PP_EXPAND in the first post) to overcome VC++ preprocessor bug, see for details. I used GCC, it does not need one.

Donation answered 23/7, 2019 at 15:7 Comment(0)
P
2

There is C++ STL algorithm to do the same:

max_element.

min_element

Starts using these instead of writing the macro to achieve this:

 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;
Palaeobotany answered 1/4, 2014 at 1:45 Comment(3)
hi, i will not use this code, i asked this question, because i want to study this technique of preprocessor, thanks a lot...Underdone
@Hikari: There is almost no need to write the MACRO in pure C++. These STL algorithm would be as efficient(almost) as the MACRO. You may also use inline concept to achieve MACRO type behaviour. So you should study these concept instead of MACRO..The choice is yours :)Palaeobotany
@tmp It could still be an interesting learning exercise. We can't read Hikari's mind.Slump
G
2

Using Boost.Preprocessor, you can implement it like this:

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

Because the preprocessor doesn't directly support comparison during expansion, It is a lot of work to implement all this from scratch. Using techniques here, you can implement a counter and a while loop construct. With that you can implement subtraction, which will allow you to implement less than(or greater than), which is needed for MAX. Then with another while you can do a fold over the varidiac arguments.

Finally, there are some limitations to doing all this in the preprocessor. The preprocessr is not entirely turing complete. So in the boost example, you would be limited to values from 0 to 256(this is entirely a boost limitation, if you do it yourself you could raise that limitation). Depending on what you want to achieve, it might be better to write a varidiac function for 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...)); 
}
Gene answered 4/4, 2014 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.