48

I've run into a little theoretical problem. In a piece of code I'm maintaining there's a set of macros like

#define MAX_OF_2(a, b)       (a) > (b) ? (a) : (b)
#define MAX_OF_3(a, b, c)    MAX_OF_2(MAX_OF_2(a, b), c)
#define MAX_OF_4(a, b, c, d) MAX_OF_2(MAX_OF_3(a, b, c), d)
...etc up to MAX_OF_8

What I'd like to do is replace them with something like this:

/* Base case #1, single input */
#define MAX_OF_N(x)      (x)

/* Base case #2, two inputs */
#define MAX_OF_N(x, y)   (x) > (y) ? (x) : (y)

/* Recursive definition, arbitrary number of inputs */
#define MAX_OF_N(x, ...) MAX_OF_N(x, MAX_OF_N(__VA_ARGS__))

...which, of course, is not valid preprocessor code.

Ignoring that this particular case should probably be solved using a function rather than a preprocessor macro, is it possible to define a variadic MAX_OF_N() macro?

Just for clarity, the end result should be a single macro that takes an arbitrary number of parameters and evaluates to the largest of them. I've got an odd feeling that this should be possible, but I'm not seeing how.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Christoffer
  • 12,712
  • 7
  • 37
  • 53

7 Answers7

48

It's possible to write a macro that evaluates to the number of arguments it's called with. (I couldn't find a link to the place where I first saw it.) So you could write MAX_OF_N() that would work as you'd like, but you'd still need all the numbered macros up until some limit:

#define MAX_OF_1(a)         (a)         
#define MAX_OF_2(a,b)       max(a, b)

#define MAX_OF_3(a,...)    MAX_OF_2(a,MAX_OF_2(__VA_ARGS__))
#define MAX_OF_4(a,...)    MAX_OF_2(a,MAX_OF_3(__VA_ARGS__))
#define MAX_OF_5(a,...)    MAX_OF_2(a,MAX_OF_4(__VA_ARGS__))
...
#define MAX_OF_64(a,...)   MAX_OF_2(a,MAX_OF_63(__VA_ARGS__))

// NUM_ARGS(...) evaluates to the literal number of the passed-in arguments.
#define _NUM_ARGS2(X,X64,X63,X62,X61,X60,X59,X58,X57,X56,X55,X54,X53,X52,X51,X50,X49,X48,X47,X46,X45,X44,X43,X42,X41,X40,X39,X38,X37,X36,X35,X34,X33,X32,X31,X30,X29,X28,X27,X26,X25,X24,X23,X22,X21,X20,X19,X18,X17,X16,X15,X14,X13,X12,X11,X10,X9,X8,X7,X6,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0, __VA_ARGS__ ,64,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 _MAX_OF_N3(N, ...) MAX_OF_ ## N(__VA_ARGS__)
#define _MAX_OF_N2(N, ...) _MAX_OF_N3(N, __VA_ARGS__)
#define MAX_OF_N(...)      _MAX_OF_N2(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

Now MAX_OF_N(a,b,c,d,e) will evaluate to max(a, max(b, max(c, max(d, e)))). (I've tested on gcc 4.2.1.)

Note that it's critical that the base case (MAX_OF_2) doesn't repeat its arguments more than once in the expansion (which is why I put max in this example). Otherwise, you'd be doubling the length of the expansion for every level, so you can imagine what will happen with 64 arguments :)

Andrew
  • 5,839
  • 1
  • 51
  • 72
DS.
  • 22,632
  • 6
  • 47
  • 54
  • Why do you have `_MAX_OF_N2`, what is it's function? – ulidtko Nov 21 '11 at 00:08
  • 2
    That's just to force one level of expansion. If I had put _MAX_OF_N3(NUM_ARGS(__VA_ARGS__), etc)", skipping the intermediate step, it would've gotten expanded to MAX_OF_NUMARGS(__VA_ARGS__)(etc). – DS. Nov 21 '11 at 20:19
  • @Ameen Variadic macros were introduced in the C language in the year 1999. Visual Studio "2017" is not modern enough, as it has not yet been fully updated to the year 1999. Use a modern C compiler instead. – Lundin Mar 16 '18 at 08:58
13

You might consider this cheating, since it is not recursive and it doesn't do the work in the preprocessor. And it uses a GCC extension. And it only works for one type. It is, however, a variadic MAX_OF_N macro:

#include <iostream>
#include <algorithm>

#define MAX_OF_N(...) ({\
        int ra[] = { __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra) / sizeof(int)]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(1, 3, i, 6);
}

Oh yes, and because of the potential variable expression in the initializer list, I don't think that an equivalent of this (using its own function to avoid std::max_element) would work in C89. But I'm not sure variadic macros are in C89 either.

Here's something that I think gets around the "only one type" restriction. It's getting a bit hairy, though:

#include <iostream>
#include <algorithm>

#define MAX_OF_N(x, ...) ({\
        typeof(x) ra[] = { (x), __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra)/sizeof(ra[0])]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(i + 1, 1, 3, 6, i);
}
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 3
    afaik, variadic macros aren't part of C++, so almost any combination becomes here hairy. :) – quinmars May 05 '09 at 16:04
  • Ah, good point, although they're available by default in g++. I was actually thinking that it's hairy because the first item in the parameter list isn't necessarily the one with the most sensible common type. Very Bad Things could happen if the array initialization slices objects or (in C) performs inappropriate conversions. – Steve Jessop May 05 '09 at 16:43
12

No, because the preprocessor only takes one "swipe" at the file. There's no way to get it to recursively define macros.

The only code that I've seen do something like this was not variadic, but used default values the user had to pass:

x = MAX_OF_8 (a, b, -1, -1, -1, -1, -1, -1)

assuming all values were non-negative.

Inline functions should give you the same for C++ at least. As you state, it's probably better left to a function with variable arguments similar to printf().

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • I'll accept this as the most correct answer, it is not (and indeed should not be) possible to create recursive preprocessor statements. – Christoffer May 06 '09 at 20:27
  • 6
    It is supposed to be impossible, but not because the preprocessor is single pass. The macro substitution process is recursive and multiple-pass, due to what the standards call "rescanning and further replacement." Recursion and corecursion are forbidden by a specifically defined mechanism. But that can still be defeated. As for recursion *using this syntax*, it's impossible because the lack of overloading precludes writing a terminating case. – Potatoswatter Mar 26 '12 at 04:35
  • 1
    You can get the preprocessor to perform multiple passes on a file with `#include __FILE__`. – Paul Brannan Apr 27 '17 at 16:12
7

I think that, even if you could expand macros recursively, there would be one little problem with your approach in terms of efficiency... when the macros are expanded, if the MAX_OF_[N-1] is greater, then you have to evaluate it again from scratch.

Here is a silly and stupid answer that probably no one will like xD

file "source.c"

#include "my_macros.h"
...

file "Makefile"

myprogram: source.c my_macros.h
 gcc source.c -o myprogram

my_macros.h: make_macros.py
 python make_macros.py > my_macros.h

file "make_macros.py"

def split(l):
    n = len(l)
    return l[:n/2], l[n/2:]

def gen_param_seq(n):
    return [chr(i + ord("A")) for i in range(n)]

def make_max(a, b):
    if len(a) == 1:
        parta = "("+a[0]+")"
    else:
        parta = make_max(*split(a))

    if len(b) == 1:
        partb = "("+b[0]+")"
    else:
        partb = make_max(*split(b))

    return "("+parta +">"+partb+"?"+parta+":"+partb+")"

for i in range(2, 9):
    p = gen_param_seq(i)
    print "#define MAX_"+str(i)+"("+", ".join(p)+") "+make_max(*split(p))

then you'll have those pretty macros defined:

#define MAX_2(A, B) ((A)>(B)?(A):(B))
#define MAX_3(A, B, C) ((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))
#define MAX_4(A, B, C, D) (((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))
#define MAX_5(A, B, C, D, E) (((A)>(B)?(A):(B))>((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E)))?((A)>(B)?(A):(B)):((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E))))
#define MAX_6(A, B, C, D, E, F) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F))))
#define MAX_7(A, B, C, D, E, F, G) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G))))
#define MAX_8(A, B, C, D, E, F, G, H) ((((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))>(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H)))?(((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D))):(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H))))

and the best thing about it is that... it works ^_^

fortran
  • 74,053
  • 25
  • 135
  • 175
  • 1
    Hah, I like this. What's not to like about using python as a C preprocessor? :) – Christoffer May 22 '09 at 07:05
  • 1
    you could use whatever cool language you like as preprocessor... and given the quantity of parens, maybe lisp could be very fun! XD or maybe a Perl one liner so you can pass the macros via -D flag to the compiler in the command line ;-) – fortran May 22 '09 at 08:40
5

If you're going down this road in C++, take a look at template metaprogramming. It's not pretty, and it may not solve your exact problem, but it will handle recursion.

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 2
    +1. Variadic templates are coming in C++1x, and g++ already implements them, but it will be a while before they are widely accepted. Sadly a template metaprogramming solution without them will still necessitate ugly nesting of terms (e.g. "max_of<3, max_of<4, max_of<5, 6> > >") unless you manually "unroll" them (which you could do just as easily with preprocessor macros). Templates are still better because they can recurse and don't evaluate arguments more than once. – j_random_hacker May 05 '09 at 12:44
4

First, macros don't expand recusrsively. Although, macros can have reentrance by creating a macro for each recursion level and then deducing the recursion level. However, all this repetition and deducing recursion, is taken care of by the Boost.Preprocessor library. You can therefore use the higher order fold macro to calculate the max:

#define MAX_EACH(s, x, y) BOOST_PP_IF(BOOST_PP_GREATER_EQUAL(x, y), x, y)
#define MAX(...) BOOST_PP_SEQ_FOLD_LEFT(MAX_EACH, 0, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) 

MAX(3, 6, 8) //Outputs 8
MAX(4, 5, 9, 2) //Outputs 9

Now, this will understand literal numbers between 0-256. It wont work on C++ variables or expression, because the C preprocessor doesn't understand C++. Its just pure text replacement. But C++ provides a feature called a "function" that will work on C++ expressions, and you can use it to calculate the max value.

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

template<class X, class... T>
auto max(X x, T ... args) -> decltype(max(x, max(args...)))
{
    return max(x, max(args...));
}

Now, the code above does require a C++11 compiler. If you are using C++03, you can create multiple overloads of the function in order to simulate variadic parameters. Furthermore, we can use the preprocessor to generate this repetitive code for us(thats what it is there for). So in C++03, you can write this:

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

#define MAX_FUNCTION(z, n, data) \
template<class T> \
T max(T x, BOOST_PP_ENUM_PARAMS(n, T x)) \
{ \
    return max(x, max(BOOST_PP_ENUM_PARAMS(n, x)));\
}

BOOST_PP_REPEAT_FROM_TO(2, 64, MAX_FUNCTION, ~) 
Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59
0

There's a nice recursion example here

The "hack" is to have a mid-step in the preprocessor to make it think that the define is not replaced by anything else at a given step.

Itay Bianco
  • 697
  • 6
  • 16