2

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 ?

18
  • 3
    This is a extraordinary bad idea. Don't do this at all. Your code will be too hard to grasp for anyone but you. Commented Dec 9, 2021 at 21:24
  • 1
    taking a course in job security? Commented Dec 9, 2021 at 21:25
  • 1
    @thebusybee I said I'm experimenting with abusing the compiler. Obviously this is stupid. It's just a learning experience for me. Commented Dec 9, 2021 at 21:26
  • 4
    But if you insist on delving into a realm of unmaintainable coding horrors, just look for the Microsoft abominations push_macro and pop_macro... And now I'll go write 2,000 lines of strictly-conforming C code as penance for posting that... Commented Dec 9, 2021 at 22:00
  • 2
    The basic point is that you shouldn't define macros with the same names as functions, variables, or language keywords. That's where the convention of using all-uppercase names for macros comes from -- since there are no keywords like that, and we don't normally use that style for variables and functions, no problems occur. Commented Dec 10, 2021 at 1:33

2 Answers 2

4

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. However, this will not work well if you do a if(x) break inside a loop. 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    }

Do note however, that both of these might fail if you have a break statement in them like this:

for(...) {
    if(x)
    do
        break;
    done
}

Because that would expand to:

for(...) {
    for(int c=1 ; c && (b = !!(x)); c=0)
    {
        break;
    }
}

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.

Sign up to request clarification or add additional context in comments.

18 Comments

This is kind of what I'm aiming towards I think. Either this, use capitals for my macros or even worse, write #define else } else which skips the {, letting it work again but now requiring else do (and thus every time I write normal C code, if { } else { } becomes if { else { } which is just horribly cursed.
I'm afraid your idea does not work if the expression x contains a reference to a variable c defined in the current scope, eg: if ((c = getchar()) != EOF)... You should use a less likely name such as c__such_a_shame__42
Also here is a simpler elif: #define elif(x) } while(!b && (b=!!(x))) {
@klutt This is incredible! Yes! Just the kind of disgusting solution I'm looking for. Thank you.
@agregate It would reduce the shortcoming. I guess size_t would reduce it even further. But !! eliminates it. At least that's my guess.
|
2

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    }}

In addition to the elif shortcoming, this solution does not mix well with user written loops and switches. These cases will be mishandled:

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

switch (x) {
  case 0:
    if (...) break;
    else { y = 42; break; }
  ...
}

For your purpose, using uppercase pseudo-keywords seems a better approach (and purposely ugly looking).

#define IF(x)   if (x)
#define DO      {
#define ELIF(x) } else if (x) {
#define ELSE    } else {
#define DONE    }

1 Comment

Nice one. You could also add your while refinement. I can only upvote once though. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.