It's been a while since I had occasion to do anything blisteringly unpleasant with the C preprocessor. But I did today, so here it is.
If you have a set of conditions such that one of them is always supposed to be true, you might plausibly find yourself wanting to write code along the lines of
if (condition1)
statement1;
else if (condition2)
statement2;
else if (condition3)
statement3;
The trouble is, if you know one of the conditions is always supposed to be true but the compiler doesn't, this sort of code can get you sworn at for sins of the order of failing to reliably initialise a variable (if each of the then-
else /* if (condition3) */
statement3;
If you just want to put in an assert as protection against muppets passing invalid data to your code, you could add it in an additional else clause:
else if (condition3)
statement3;
else
assert(!"We shouldn't be here");
If you want both the above advantages, the best idiom I know of looks like this:
else {
assert(condition3);
statement3;
}
But that last one is particularly nasty in terms of readability, because that final else-
if (condition1)
statement1;
else if (condition2)
statement2;
else since (condition3)
statement3;
using the non-since to mean ‘enforce by assertion that condition3 is true, and then execute statement3’.
So of course I wondered if since could be implemented using preprocessor hackery. It doesn't look easy at first glance, because what I'm essentially trying to do is construct a statement block containing my assert statement followed by statement3, and all the obvious ways to do that require a closing brace after statement3, which I can't arrange using the invocation syntax shown. But with a bit more thought, it turns out it can be done.
To begin with, we need a piece of standard boilerplate which should be boringly familiar to anyone who's ever tried to do anything complicated with the C preprocessor:
#define CAT2(x,y) x ## y
#define CAT(x,y) CAT2(x,y)
This enables me to write CAT(x,y) and get a single token formed by gluing together the results of macro-x and y.
Now I can write since:
#define since(condition) \
if (1) { \
assert(condition); \
goto CAT(sincelabel,__LINE__); \
} else \
CAT(sincelabel,__LINE__):
That works regardless of whether statement3 is itself a braced block or not, and it's portable ISO C as far as I'm aware. The only restriction is that you mustn't put two since statements on the same source line (unless they're in different label scopes!).
Now I just need to decide whether I'm brave enough to actually check it into my code :-)
What's wrong with:
(assert is required to expand to a void expression.)
#ifdef NDEBUG
#define since(cond) if (0) ; else
#else
#define since(cond) if (!(cond)) { assert(0); } else
#endif
As a side note, since() plays merry hell with emacs's formatting…
sinceis anif.