Skip to content

C Preprocessor tricks, tips, and idioms

pfultz2 edited this page Jun 21, 2012 · 14 revisions

Basic

Pattern Matching

The ## operator is used to concatenate two tokens into one token. This is provides a very powerful way to do pattern matching. Say we want to write a IIF macro, we could write it like this:

#define IIF(cond) IIF_ ## cond
#define IIF_0(t, f) f
#define IIF_1(t, f) t

However there is one problem with this approach. A subtle side effect of the ## operator is that it inhibits expansion. Heres an example:

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// This will however expand to IIF_A()(true, false)
// This is because A() doesn't expand to 1,
// because its inhibited by the ## operator
IIF(A())(true, false) 

The way to work around this is to use another indirection. Sense this is commonly done we can write a macro called CAT that will concatenate without inhibition.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

So now we can write the IIF macro(its called IIF right now, later we will show how to define a more generalized way of defining an IF macro):

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define A() 1
//This correctly expands to true
IIF(1)(true, false) 
// And this will also now correctly expand to true
IIF(A())(true, false) 

With pattern matching we can define other operations, such as COMPL which takes the complement:

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

We can define increment and decrement operators as macros:

#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8

Detection

Detection techniques can be used to detect if the parameter is a certain value or if it is parenthesis. It relies on vardiac arguments expanding to different number of parameters. At the core of detection is a CHECK macro with a PROBE macro like this:

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,

This is very simple. When the probe is given to the CHECK macro like this:

CHECK(PROBE(~)) // Expands to 1

But if we give it a single token:

CHECK(xxx) // Expands to 0

So with this, we can create some detection macros. For instance, if we want to detect for parenthesis:

#define IS_PAREN(x) IS_PAREN_PROBE x
#define IS_PAREN_PROBE(...) PROBE(~)
IS_PAREN(()) // Expands to 1
IS_PAREN(xxx) // Expands to 0

Now, say we want to write a generalized IF macro that picks false if its 0 otherwise it would pick true. We can use these detection techniques to do that. First, we start by writing a NOT operator which is just like an IS_0 macro:

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)

Next we use the COMPL macro that we wrote to inverse this result, and then call IIF:

#define BOOL(x) COMPL(NOT(x))
#define IF(c) IIF(BOOL(c))

Recursion

Unfortunately, macros can't expand recursively. When a macro expands its painted blue, and can no longer expand for that scan. First, there are ways to work around this to keep macros expanding. Secondly, we can detect if a macro is painted blue(because it wont expand) and use this state to expand to a different macro.

Deferred expression

Clone this wiki locally