Wait, was there any recent change to C23 that enabled a different solution than `__COUNTER__`? I realized you have mentioned but didn't fully define `CLEANSE_MACRO_VARS` in recent comments, is there any other pointer?
Yes, implementing `CLEANSE_MACRO_VARS` was left as an exercise for the reader. The real key improvement here is that C23 standardized C++-style `auto` (or GNU C `__auto_type`), though its improved support for variadic macros also helps a few corners.
The key observation is that, for expression-like (only with GNU statement expressions) or statement-like (using do-while, possibly emulating expression-like macros by specifying an output variable) macros, shadowing is perfectly okay as long as it doesn't happen before the evaluation of the macro argument expressions.
So what you do is define all the variables twice - first, with long and ugly names (which name is only generated/used deep inside the cleansing macro), to capture the expression, and then (after all the ugly definitions are done, not just some), as a simple copy of that with a nice name for use in the user's macro. So the resultant macro works like:
#define MAX(a_, b_) // omitting backslashes
({
auto _ugly_a = (a_);
auto _ugly_b = (b_);
// it's safe if the above arguments expand to contain unrelated `a` and `b`.
auto a = _ugly_a;
auto b = _ugly_b;
// in a statement expression, the last statement is the resulting value
a > b ? a : b;
})
There's no need to `__LINE__` for this particular problem, since all you need is a sufficiently long unique prefix to namespace it (which all C libraries assume anyway).
=====
Note also that the old `__typeof__` is not useless just because `__auto_type` exists. Besides things like `_Generic` and function definitions where there's no initializer allowed, it's also useful for safely forming pointers to a passed-in type which might be (a pointer to) an array or function, since, like `sizeof`, types are also valid for its argument. This is in fact in the documentation if anybody reads that.
Ah, I see. I thought the cleaning involved something like gensym, but you just meant that a new scope can introduce a variable of the same name without interfering with the original. You don't really forbid something like `MAX(&_ugly_a, &_ugly_b)` for example, that was why I was confused.