Saturday, June 23, 2012

A Macro Interlude

Or... Traversing Macro Pasting Hell

Full disclaimer: This article used to be very different but was also complete tosh. The code I wrote based on its assumptions and misinformation was working fine for days and then broke around 5 minutes after posting the article when I discovered a new use case. This new article addresses that use case and the subsequent solution

Macro pasting

The C preprocessor has a handy (if not essential) operator for pasting 2 tokens together, ##. I use it all over the place in my OOOCode project to generate class and function names, etc using macros. It can be used like this...

#define PASTE(ARG1, ARG2) ARG1 ## ARG2
PASTE(Hello, World)();
/* expands to */
HelloWorld();
view raw Paste.c hosted with ❤ by GitHub

Now consider the following...

#define PASTE(ARG1, ARG2) ARG1 ## ARG2
#define HELLO Hello
PASTE(HELLO, World)();
/* expands to */
HELLOWorld();
view raw PasteBroken.c hosted with ❤ by GitHub

In this second example I have passed a macro in as an argument to the PASTE macro and as a result it does not get expanded. In order to fix this it is necessary to add a level of indirection...

#define _PASTE(ARG1, ARG2) ARG1 ## ARG2
#define PASTE(ARG1, ARG2) PASTE(ARG1, ARG2)
#define HELLO Hello
PASTE(HELLO, World)();
/* expands to */
HelloWorld();
view raw PasteFixed.c hosted with ❤ by GitHub

As an aside, this same problem (and solution) occurs with the quoting operator, #, too.

Variadic macros and swallowing extra commas

Now it gets interesting as the pasting operator can also be used in variadic macros to swallow commas when no arguments are provided...

#define CALLONE(FUNCTION, ARGS...) FUNCTION(1 , ##ARGS)
CALLONE(myFunc);
/* expands to */
myFunc(1);
view raw VariadicMacro.c hosted with ❤ by GitHub

Handy, yeah? Well sort of. The problem is that this still exhibits the same problems as above when macros are used as arguments...

Macro pasting variadic hell

#define CALLONE(FUNCTION, ARGS...) FUNCTION(1 , ##ARGS)
CALLONE(add, CALLONE(addOne));
/* expands to */
add(1, CALLONE(addOne));
view raw FailsToExpand.c hosted with ❤ by GitHub

The above code does not compile as the second macro call does not get expanded. So I tried this...

#define _CALLONE(FUNCTION, ARGS...) FUNCTION(1 , ##ARGS)
#define CALLONE(FUNCTION, ARGS...) _CALLONE(FUNCTION, ARGS)
CALLONE(add, CALLONE(addOne));
/* expands to */
add(1, addOne(1, ));

I'm not sure if this would work in other environments but the C Preprocessor that comes with the OpenTV IDE doesn't swallow the comma in this case.

This gave me a big problem. I can either support macros as arguments or zero length argument lists... but not both :(

Believe me I tried a great many more constructions involving the ## operator and various indirections but to no avail. It just wasn't happening. Eventually (it was quite long time that may even have involved praying as I was a long way into my OOOCode stuff and this was pretty key) I came across a different solution involving detecting empty argument lists. Doing this is not simple and definitely not something I want to get into here, but just know that in the following example the ISEMPTY macro expands to 1 if the argument list supplied is empty or 0 if not...

#define ISEMPTY(ARGS...) /* Too complicated to put here, see http://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ */
#define _PASTE(ARG1, ARG2) ARG1 ## ARG2
#define PASTE(ARG1, ARG2) PASTE(ARG1, ARG2)
#define CALLONE0(FUNCTION, ARGS...) FUNCTION(1, ARGS)
#define CALLONE1(FUNCTION, ARGS...) FUNCTION(1)
#define CALLONE(FUNCTION, ARGS...) PASTE(CALLONE,ISEMPTY(ARGS))(FUNCTION, ARGS)
CALLONE(add, CALLONE(addOne));
/* expands to */
add(1, addOne(1));
view raw Solution.c hosted with ❤ by GitHub

For the ISEMPTY macro stuff, special thanks have to go to Jens Gustedt and this article:

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

2 comments:

  1. Hi Peter,
    I have the impression that you could well profit from integrating P99 into your OO project. It already has:

    - default arguments for functions
    - type generic macros as a replacement for function overloading
    - macro conditional expansion
    - a initialization convention (AKA constructor) that allows for something like P99_NEW(toto, ... arguments)
    - exceptions that throw int's
    - threads

    Jens

    ReplyDelete
  2. Hi Jens,

    You're probably right although i'll likely have to do some porting to get it to work. In particular I can't use unnamed variadic arguments like this:

    #define MYMACRO(...) doSomething(__VA_ARGS__)

    I would have to change such macros to:

    #define MYMACRO(ARGS...) doSomething(ARGS)

    Anyway, nice to hear from you - I really did appreciate the empty argument stuff :)

    ReplyDelete