Home > Enterprise >  Disallow C preprocessor from using a macro within another
Disallow C preprocessor from using a macro within another

Time:12-11

I'm experimenting to see how far I can abuse the C preprocessor and I have stumbled across an interesting problem.

I have the following macro defines:

#define if(x)   if (x)
#define do      {
#define elif(x) } else if (x) {
#define else    } else {
#define done    }

Which should allow me to write:

if (i == 1)
do
    ...
elif (i == 2)
    ...
else
    ...
done

And it works perfectly fine if I only use if and else, except the introduction of elif is problematic because the macro expands as:

} } else { if (x) {

due to the else being defined.

Is there any way I can get elif to use 'raw' else without having it picked up by the preprocessor? I think I need to try nesting multiple defines to trick the preprocessor into pasting the word directly without parsing it but I'm not sure how to achieve this.

Any ideas, or is this not possible in GCC?

Edit:

In essence, this can be boiled down to the following problem:

#define A B
#define B C

For the two given defines A and B, how can I get A to still resolve to the literal word B and not go through the second define and end up as C ?

CodePudding user response:

Update

I think I managed to solve it. I utilized that:

if(x) {
    ...
} 

is the same as

for(; x ;) {
    ...
    break:
}

What we need from there is to save the result of x. We cannot reuse it, since x might be an expression with side effects. So:

int b;

for(; b = (x);) {

    break;
}

Now, we can check b to see if the above for loop was executed or not. A complete if-elif-else pattern done with for loops can look like this:

for(;b = (x);) { // if
    ...
    break; 
} 

for(; !b ? b=(x==1) : 0;) { // elif
    ...
    break; 
} 

for(; !b ;) { // else
    ...
    break; 
}

With that, we can wrap it up like this, but be aware. This would not work well if you do a if(x) break inside a loop, but see below.

int b; // Store truth value of last if or elif

#define if(x)   for(;b = !!(x);)
#define do      {
#define elif(x) break; }  for(; !b ? b=!!(x) : 0;) {
#define else    break; }  for(;!b;) { 
#define done    break; }

Demo: https://onlinegdb.com/Zq6Y7vm5Q

An alternative approach without break statements:

int b; // Store truth value of last if or elif

#define if(x)   for(int c=1 ; c && (b = !!(x)); c=0)
#define do      {
#define elif(x) }  for(int c=1; c && (!b ? b=!!(x) : 0); c=0) {
#define else    }  for(int c=1; c && !b; c=0) { 
#define done    }

This one is preferable, because if you use the first one and do something like:

for(...) {
    if(...) break;
}

you would not get the desired result.

Note:

It should be obvious, but if you decide to use this code (don't) then use better names than b and c to avoid collisions.

Old workaround

Not quite what you asked for, but you have admitted that you're basically just abusing the preprocessor. :)

But an easy workaround is to use a synonym for else.

#define if(x)      if (x)
#define do         {
#define elif(x)    } else if (x) {
#define otherwise  } else {
#define done       }

Demo: https://onlinegdb.com/Cp-gYpOvm

It works with zero, one or multiple instances of elif, and regardless of how many elifs, it works with and without otherwise.

CodePudding user response:

Building on klutt's ideas, here is an alternative without the need for an extra variable, but limited to a single elif clause:

#define if(x)   switch (!!(x))
#define do      { case 1: {
#define elif(x) } break; default: switch (!!(x)) { case 1:
#define else    } break; default: {
#define done    }}
  • Related