Evil preprocessor hack of the day [entries|reading|network|archive]
simont

[ userinfo | dreamwidth userinfo ]
[ archive | journal archive ]

Wed 2007-04-18 17:39
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 :-)

LinkReply
[identity profile] ewx.livejournal.comWed 2007-04-18 16:59

What's wrong with:

#define since(x) if(assert(x), 1)

(assert is required to expand to a void expression.)

Link Reply to this | Thread
[personal profile] simontWed 2007-04-18 17:25
Sensible standards requirements spoil all my fun ("you insensitive clod"). My technique, however, is extensible to other similar situations in which that guarantee isn't present :-P
Link Reply to this | Parent
[personal profile] fanfWed 2007-04-18 17:30
*snap*
Link Reply to this | Parent
[identity profile] tackline.livejournal.comWed 2007-04-18 17:57
I was thinking (and I've not been a C++ programmer for almost a decade(:

#ifdef NDEBUG
#define since(cond) if (0) ; else
#else
#define since(cond) if (!(cond)) { assert(0); } else
#endif

Link Reply to this | Parent
[personal profile] gerald_duckWed 2007-04-18 20:28
Why does it matter what assert() returns?

As a side note, since() plays merry hell with emacs's formatting…
Link Reply to this | Parent | Thread
[identity profile] gareth-rees.livejournal.comWed 2007-04-18 21:18
This could be fixed by hacking c-block-stmt-2-kwds in cc-langs.el, I think. But definitely a point against, yes.
Link Reply to this | Parent | Thread
[identity profile] ewx.livejournal.comWed 2007-04-18 21:27
It's sufficiently simple that you might just type it out rather than using a macro anyway.
Link Reply to this | Parent
[identity profile] ewx.livejournal.comWed 2007-04-18 21:25
The important point is that it expands to an expression, rather than (for instance) an if statement.
Link Reply to this | Parent
[identity profile] mooism.livejournal.comThu 2007-04-19 08:36
We want
if (foo)  handle_foo();
else if (bar)  handle_bar();
else since (baz)  handle_baz();
else since (quuz)  handle_quux();
to give a compile error.
Link Reply to this | Parent | Thread
[identity profile] mooism.livejournal.comThu 2007-04-19 08:38
Better example if the second since is an if.
Link Reply to this | Parent | Thread
[identity profile] womble2.livejournal.comThu 2007-04-19 22:29
gcc -Wunreachable-code -Werror
Link Reply to this | Parent | Thread
[identity profile] ewx.livejournal.comFri 2007-04-20 12:45
Or perhaps: #define since(x) if(assert(x),0);else (though I've not thought about this very hard yet).
Link Reply to this | Parent
[identity profile] geekette8.livejournal.comWed 2007-04-18 19:55
OK, I have done [what I consider to be relatively] complicated things with the C pre-processor, but I don't understand why you needed two levels of CAT definition. What's wrong with
#define CAT(x,y) x ## y
?
Link Reply to this | Thread
[personal profile] simontWed 2007-04-18 20:07
If you do that, then CAT(foo,__LINE__) gives you the fixed token foo__LINE__, instead of giving you foo23 or foo5097 or other such things.
Link Reply to this | Parent | Thread
[identity profile] geekette8.livejournal.comWed 2007-04-18 20:42
Ah, gotcha. Thank you!
Link Reply to this | Parent
[identity profile] cartesiandaemon.livejournal.comSun 2007-04-22 17:05
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.

ROFL. I love the way you say that.

In general. Yes, I agree, a spectacular epitome of preprocessor hacking.

What I'm thinking at the moment is: There should be a standard, sane way of adding keyword constructs to the language in such a way without using stupid text processing provided by the preprocessor, but also without encouraging a mad proliferation of every word anyone thinks might be useful, making ported code unreadable.

So, "since" and "with" might catch on, or certain other changes that seem useful (eg. new operators), but no more. Does that make sense?
Link Reply to this
navigation
[ go | Previous Entry | Next Entry ]
[ add | to Memories ]