Blue Paint and Deferred Expansions: The C Preprocessor as an Accidental Metaprogramming Language
Source: lobsters
The C preprocessor was not designed to do what Paul Fultz II’s Cloak library makes it do. It was built for text substitution, include guards, and conditional compilation. It has no loop construct, no recursion, no first-class data structures, and it deliberately refuses to expand the same macro twice during a single expansion pass. And yet, with enough precision about exactly when token expansions happen, you can simulate recursion, write a REPEAT(N, macro) facility, implement boolean logic, and do arithmetic on small integers, all in standard C.
Understanding why this works requires understanding why it almost doesn’t.
The Blue Paint Rule
The core constraint is in C11 section 6.10.3.4: once a macro begins expanding, its name is marked for the duration of that expansion. Any token in the resulting output that matches the macro’s own name is left as a literal identifier and not re-expanded. This exists to prevent infinite loops, and it works exactly as intended.
#define DOUBLE(x) x + x
DOUBLE(DOUBLE(3))
// Outer DOUBLE fires, produces: DOUBLE(3) + DOUBLE(3)
// The inner DOUBLEs are now "painted blue" and left as plain tokens
// Final result: DOUBLE(3) + DOUBLE(3)
That’s the blue paint rule. It makes the preprocessor look incapable of anything interesting. The key insight is that blue paint only lasts for the duration of one macro’s expansion. Once that expansion finishes and the preprocessor’s rescan pass begins on fresh output, tokens that were previously blue can become expandable again, but only if they appear in a syntactically valid call form at the right moment.
Cloak’s tricks are entirely about controlling when that moment arrives.
EMPTY and DEFER
The foundational primitive is this pair:
#define EMPTY()
#define DEFER(id) id EMPTY()
EMPTY() takes no arguments and expands to nothing. DEFER(SOME_MACRO) produces the token sequence SOME_MACRO EMPTY(). The preprocessor sees SOME_MACRO and then immediately encounters EMPTY(), which it expands first. After EMPTY() disappears, SOME_MACRO is left standing alone without an argument list following it, so it is not treated as a macro invocation in this pass. It survives as a plain token.
In the next rescan, SOME_MACRO is adjacent to whatever tokens follow in context. If an argument list appears there, the macro fires. If not, it doesn’t. The expansion was deferred by exactly one pass.
You can add another level:
#define OBSTRUCT(id) id DEFER(EMPTY)()
OBSTRUCT(SOME_MACRO) produces SOME_MACRO DEFER(EMPTY)(). In the first rescan, DEFER(EMPTY)() expands to EMPTY EMPTY()(). In the next pass, EMPTY() expands to nothing, leaving EMPTY(). On the third pass, that last EMPTY() resolves and SOME_MACRO finally gets a following (). Each additional obstruction layer buys one more pass of delay.
EVAL: Forcing Multiple Rescan Passes
Deferred expressions only pay off if the preprocessor actually performs multiple rescans. By default, each macro call triggers one expansion pass on its result. The EVAL macro forces N nested passes by chaining macros whose only job is to re-expand their variadic argument:
#define EVAL(...) EVAL1(EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(EVAL3(EVAL3(__VA_ARGS__)))))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(EVAL4(EVAL4(__VA_ARGS__)))))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(EVAL5(EVAL5(__VA_ARGS__)))))
#define EVAL5(...) __VA_ARGS__
This nests to 5^5 = 3125 expansion passes. Each EVALn level forces its contents through another pass, unpeeling one layer of DEFER or OBSTRUCT per iteration. Cloak’s header defines exactly this structure, and it is what enables everything else in the library.
Conditionals, Increment, and Recursion
With EVAL in place, you can build the rest of the facility. Boolean conditionals work through token pasting:
#define CAT(a, b) a ## b
#define IIF(c) CAT(IIF_, c)
#define IIF_0(t, f) f
#define IIF_1(t, f) t
IIF(1)(then_branch, else_branch) pastes IIF_ and 1 to form IIF_1, then calls it with the two branches. This works because token pasting happens before macro expansion of the pasted result, so IIF_1 gets resolved in a fresh context without any blue paint issues from IIF.
Small integer arithmetic is handled by lookup tables:
#define INC(x) CAT(INC_, x)
#define INC_0 1
#define INC_1 2
// ...
#define DEC(x) CAT(DEC_, x)
#define DEC_1 0
#define DEC_2 1
// ...
Up to some bounded range (typically 256), this gives you increment and decrement. Combined with IIF and EVAL, you can build a REPEAT macro that actually loops:
#define REPEAT_INDIRECT() REPEAT_IMPL
#define REPEAT(count, macro, ...) \
EVAL(REPEAT_INDIRECT()(count, macro, __VA_ARGS__))
#define REPEAT_IMPL(count, macro, ...) \
IIF(BOOL(count))( \
DEFER(REPEAT_INDIRECT)()(DEC(count), macro, __VA_ARGS__) \
macro(count, __VA_ARGS__), \
)
The indirection through REPEAT_INDIRECT is essential. REPEAT is blue during its own expansion, so calling it directly from inside REPEAT_IMPL would produce a dead token. By calling an intermediary macro that itself returns REPEAT_IMPL, the recursion goes through a fresh name each time and avoids the blue paint entirely. EVAL drives enough passes to fully unroll the loop up to the count given.
The More Accessible Pattern: X-Macros
Most real-world C code that benefits from preprocessor metaprogramming does not need EVAL or OBSTRUCT. The X-macro pattern achieves a lot with much simpler machinery:
// In a header: colors.h
X(RED, 0xFF0000)
X(GREEN, 0x00FF00)
X(BLUE, 0x0000FF)
// Enum generation
#define X(name, val) name = val,
typedef enum {
#include "colors.h"
} Color;
#undef X
// String table generation
#define X(name, val) [name] = #name,
static const char* color_names[] = {
#include "colors.h"
};
#undef X
The single source of truth lives in colors.h, and every derived representation stays automatically synchronized. No recursion, no EVAL, no deferred anything. The Linux kernel uses this pattern extensively for things like syscall tables and device capability flags, and it predates Cloak by decades.
Boost.Preprocessor and the Prior Art
Boost.Preprocessor has provided a comprehensive, battle-tested preprocessor metaprogramming library for C and C++ since around 2001. It covers iteration (BOOST_PP_REPEAT, BOOST_PP_FOR), arithmetic, list and sequence manipulation, and conditional logic. It handles the blue paint problem through careful manual unrolling rather than the DEFER/EVAL approach, which means its maximum iteration counts are fixed and baked into the library at generation time rather than derived dynamically.
Cloak’s contribution is showing that the DEFER/EVAL approach unifies these patterns into a smaller set of primitives. Rather than pre-generating hundreds of helper macros for each possible iteration depth, you write EVAL once and get arbitrarily deep (up to 3125) recursion from a handful of definitions. This is a real improvement in composability, even if both approaches produce the same C output.
When This Is Worth Using
The honest answer is: rarely. C++11 gave us constexpr functions, variadic templates, and std::integer_sequence, all of which are cleaner, debuggable, and do not produce inscrutable error messages. Any project that can use C++ should.
For C-only codebases, the calculus is different. Embedded firmware, operating system kernels, hardware abstraction layers, and portable system libraries frequently operate under constraints that rule out C++. In those contexts, preprocessor metaprogramming is sometimes the only tool available for eliminating repetition across large register maps, protocol field definitions, or error code tables.
Even there, the hierarchy of preference should be: X-macros for structural repetition, Boost.Preprocessor for iteration, and Cloak-style DEFER/EVAL only when you need genuine recursive structure that neither of the simpler tools can express. The EVAL approach also has a hard ceiling: 3125 passes is generous but finite, and the compile-time cost of deeply nested macro expansion is not free.
The Deeper Point
What makes this worth studying is not that you should do it. It is that the techniques reveal something about the preprocessor’s computational model that is not obvious from normal usage. The blue paint rule, the single-pass scanning, the distinction between expansion time and rescan time: these are not arbitrary limitations. They are the precise constraints of a particular evaluation strategy, and the DEFER/OBSTRUCT pattern exploits the gaps in those constraints in a principled way.
Paul Fultz II’s Cloak wiki is worth reading as a piece of systems thinking as much as a macro reference. The preprocessor turns out to be a single-pass term rewriting system with a non-expansion rule for active macro names, and once you see it in those terms, the tricks are not surprising. They are the obvious consequence of understanding the spec more precisely than the spec’s authors may have intended.