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 ?

agregate
  • 163
  • 6
  • 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. – the busybee Dec 09 '21 at 21:24
  • 1
    taking a course in [job security](https://github.com/Droogans/unmaintainable-code)? – yano Dec 09 '21 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. – agregate Dec 09 '21 at 21:26
  • You could demonstrate the same with much less questionable (and much more understandable) example. – Eugene Sh. Dec 09 '21 at 21:27
  • And you learned that you cannot do it. Case closed. It is at least a good example how not to do it. -- BTW, this has nothing to do with GCC. Read the standard, especially the chapters on expanding macros. – the busybee Dec 09 '21 at 21:28
  • @thebusybee No I haven't, that is why I am asking the question. I am not asking for help in writing correct code, only to get this to work, so please take your warnings elsewhere. As for GCC, I'll remove the tag. – agregate Dec 09 '21 at 21:30
  • @EugeneSh. I'm sorry, English is not my native language. I'm happy to make it more clear if there is something you do not understand? – agregate Dec 09 '21 at 21:31
  • 1
    My point is that you could have `#define A B` and `#define B C` and then ask how to make `A` to expand to `B` but not `C`. – Eugene Sh. Dec 09 '21 at 21:53
  • @EugeneSh. I see. I'll add that in. Thank you. – agregate Dec 09 '21 at 21:55
  • About the only thing you can do to prevent macro expansion under the C standard is to wrap a function-type macro in parenthesis, as in `(foo)()` will call the function `foo()` even if the function-type macro `foo()` is defined. See https://stackoverflow.com/questions/15654070/invoke-function-instead-of-macro-in-c – Andrew Henle Dec 09 '21 at 21:55
  • 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... – Andrew Henle Dec 09 '21 at 22:00
  • @AndrewHenle That is a shame, I was really hoping it would be possible. I actually tried using `push_macro` and `pop_macro`, which *do* exist in GCC for compatibility. Can you think of a way to use them in this way? – agregate Dec 09 '21 at 22:02
  • Steve Bourne used a similar technique (set of macros) in writing the Bourne Shell (based on Algol notation), but ISTR he used upper-case macro names to avoid the problem you've run into. – Jonathan Leffler Dec 09 '21 at 22:02
  • @JonathanLeffler As simple as it is, I suppose that isn't a bad solution in that I could continue the abuse experiment with capitals instead. – agregate Dec 09 '21 at 22:04
  • For the macros Mr Bourne used, a Google search 'bourne shell macro abuse' leads to (among others), this [page](https://research.swtch.com/shmacro) which shows the macros and some code using them. – Jonathan Leffler Dec 09 '21 at 22:16
  • 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. – Barmar Dec 10 '21 at 01:33
  • 1
    @thebusybee Do you really mean that OP learned that it is impossible by failing to do it? That logic is seriously flawed. – klutt Dec 10 '21 at 08:04
  • @klutt No, not at all. I presumed that the OP did his homework and read the standard. And then thinking how to do it anyway with some specific compiler, and finding that it cannot be done, is the lesson. I was wrong, the OP did not read the standard. -- Fortunately Eugene suggested to refine the question. – the busybee Dec 10 '21 at 08:41

2 Answers2

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.

klutt
  • 30,332
  • 17
  • 55
  • 95
  • 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. – agregate Dec 10 '21 at 09:17
  • 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` – chqrlie Dec 10 '21 at 15:04
  • @chqrlie Yes, I know, but I thought that thing would be obvious to anyone trying this kind of stuff out. – klutt Dec 10 '21 at 15:06
  • To support all scalar types, you should write `#define if(x) for(;b = !!(x);)` and `#define elif(x) break; } for(; !b ? b=!!(x) : 0;) {` – chqrlie Dec 10 '21 at 15:17
  • @chqrlie Could you give an example where this code would fail for that reason? – klutt Dec 10 '21 at 15:18
  • It fails if the conversion produces `0` for a truth value. eg: `if (0x100000000)` on a 32-bit machine would have undefined behavior and most probably evaluate to false with your macro definition. Same for a pointer value with whose low order 32-bits happen to be null, same for `if (0.1)`... – chqrlie Dec 10 '21 at 16:06
  • 1
    Also here is a simpler elif: `#define elif(x) } while(!b && (b=!!(x))) {` – chqrlie Dec 10 '21 at 16:12
  • 1
    @klutt This is incredible! Yes! Just the kind of disgusting solution I'm looking for. Thank you. – agregate Dec 10 '21 at 17:36
  • @chqrlie In terms of my use case, this will not be an issue but may I ask why it produces UB ? – agregate Dec 10 '21 at 17:40
  • @chqrlie Got it. Changed to `!!`. Thanks. And yes, it could be a bit simpler with a while, but tbh, how much point do you see in making this mess more readable? ;) – klutt Dec 10 '21 at 20:50
  • @klutt: the point is to abuse the preprocessor and boggle the reader's mind, but this does not preclude elegance. I have an alternative using `switch`, `case` and `default` but it only supports a single `elif` clause :( – chqrlie Dec 10 '21 at 22:51
  • @agregate: with *klutt*'s initial solution, `if (0x100000000)` expanded to `for(;b = (0x100000000);)`, and `0x100000000` exceeds the range of type `int`. The C Standard specifies that when converting an integer to a new type, if the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised. So the behavior is not UB, but merely implementation defined. Most implementations will just truncated the high bit, leaving `b` with a zero value. – chqrlie Dec 10 '21 at 22:58
  • @chqrlie Well, you're free to post an answer on your own :) – klutt Dec 10 '21 at 23:02
  • @chqrlie Ahhh, so I suppose `long` would fix some of these shortcomings? – agregate Dec 10 '21 at 23:17
  • 1
    @agregate It would reduce the shortcoming. I guess `size_t` would reduce it even further. But `!!` eliminates it. At least that's my guess. – klutt Dec 11 '21 at 11:06
  • I'm afraid the case of `for(...) { if(...) break; }` remains unsolved. In both cases`break` would break out of the inner loop, not the user's. – chqrlie Dec 11 '21 at 12:55
  • @chqrlie You're right. Didn't fix it, but reworded it with a warning about that. – klutt Dec 11 '21 at 13:45
  • This will also fail if you ever decide to add your own `for` macro. – Barmar Dec 13 '21 at 22:15
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    }
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Nice one. You could also add your `while` refinement. I can only upvote once though. :) – klutt Dec 11 '21 at 11:17