|
Evil preprocessor hack of the day 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-clauses would have initialised it differently, for example). Or if you know that one of the conditions should always be true but want to protect yourself against someone using your code wrongly in future, you might also want to take further precautions. So there's a tendency to rewrite the last else-if in various ways. If you just want to placate the optimiser, you could simply omit the final condition: 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 is supposed to be semantically very similar to the previous branches, and now it looks totally different. What I really wanted to write, when this happened to me today, was this: if (condition1) statement1; else if (condition2) statement2; else since (condition3) statement3;
using the non-existent keyword 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-expanding 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 :-) |