An Invisible Gorilla in Our Enlightenment of Programming

Let me ask an “ancient” question:

How to break out of an if statement?

if(cond) {  
    work1();  
    auto result = work1();  
    if(result is not good){  
        break; // <- how to approach this?  
    }  
    a();  
    lot();  
    of();  
    other();  
    jobs();  
}

This is a very basic question, right at the beginning of your programming career, you might be taught to solve it like this:

if(cond) {  
    do {  
        finish();  
        the();  
        jobs();  
        break; // <- available  
        other();  
        work();  
    } while(false);  
}

This solution uses a pass-through do-while statement, which enables break in the internal block. But don’t you think it indent too much? And it doesn’t seem very intuitive.
I mean, the codes inside the block are executed, if, and only if both a true condition and an explicit false condition (the while(false)) are presented.

Another disadvantage is that, if you want to hide the while(false) structure, you define a macro:

#define breakable_if_begin(C) if(C) do {  
#define breakable_if_end() } while(false);  

breakable_if_begin(cond)  
    work();  
    break;  
    nomore();  
    work();  
breakable_if_end()

You must end the code block with another macro (say, breakable_if_end()). This is just silly and ugly.
I’m not writing a BEGIN_MASSIVE_MAP() or something, but only a tiny-piddling if - and I want it breakable, that’s all.

If you look up in the stackoverflow

There had been plenty of questions asked in the old days, such as the canonical thread. Many people were telling you to use goto. goto is simple, and bad, we have already known. The BEST practice of goto is to only use them in the case of breaking out of nested ifs.

Huh, what the funny. The notion is that “the best practice of goto equals break”, but not that “the best practice of approaching break equals goto”. And even “the BEST” down-through goto is not the best, because the label is always declared AFTER the goto statement, which is still counterintuitive.

Now the Invisible Gorilla shows up:

Have you ever thought, of using switch ?

Let me straightly show you the approach:

// Also Hosted on Compiler Explorer:  
// https://gcc.godbolt.org/z/38GoKTexb  

#include <cstdio>  

#define b_if(cond)                           \  
    switch (1 & static_cast<unsigned>(cond)) \  
    case 1:  

int main(int argc, char** argv) {  
    b_if(argc > 2) {  
        printf("ok\n");  
        break;  
        throw "NO!";  
    }  

    switch (1)  // Note how weird it looks  
    case 1: {  
        printf("yes\n");  
        printf("it's really working\n");  
        break;  
        throw "never here";  
    }  
}

You probably never saw this “gorilla stuff” before. I have searched the entire internet (in both English and Chinese), and couldn’t find anyone talking about this at all. I guess people rarely realize the switch statement also has “the single statement form”.

Actually, there isn’t any “form” of statements, only types:

C++ includes the following types of statements:

  1. labeled statements;
  2. expression statements;
  3. compound statements;
  4. selection statements;
  5. iteration statements;
  6. jump statements;
  7. declaration statements;
  8. try blocks;
  9. atomic and synchronized blocks (TM TS).

The switch is a selection statement, defined as follows:

attr(optional) switch ( init-statement(optional) condition ) statement

Compared to the if statement:

attr(optional) if constexpr(optional) ( init-statement(optional) condition ) statement

As we can see their syntax is almost identical. The trailing statement part of each is exactly the same. So if we can write

if(cond) work();

We should be able to write

switch(cond) work();

And yes, it’s totally legitimate. Except it won’t do anything nor be compiled to any codes, since no cases can be matched.

But appending a case clause makes all the sense.

  • case is a labled statement, having the form:

    label : statement

    where label represents the case <constexpr> part.

  • The statement part again can be any type of statement, of course including the compound statement ({}, AKA “a block”).

  • In a compound statement, there can be any number of statements.

  • Remember, it’s still inside the switch statement, meaning the break statement is also available.

Finally there is the most interesting fact: How to express else?

Just write a default. Like this:

switch(cond) case 1:{  
    puts("when ");  
    puts("true.");  

    default: // <- that's it  

    // all the follows are in the "else" routine  
    puts("when ");  
    puts("false.");  
}

Yes, since it’s yet inside the switch statement. Clear enough, isn’t it?

This little discovery surprised me about two things.

1. “The standard” may differ a lot from “what we learned”.

When we were pupils, we didn’t “learn by studying”, but “by impartation”. Although we read the books and acquire knowledge by ourselves, the content is selectively chosen and specifically presented by the author, which may be still far from its original appearance. Something may be ignored, something may be twisted or slightly different in details. We learned an impression, not the matter.

But a standard must be very concrete, very specific. The standard may be challenged by a variety of strange interpretations and corner cases during drafting. So the final result is always unambiguous and well-categorized, to distinguish the right behavior from others. Like, I was looking forward to checking “the syntax of switch statement”, but the standard tells me “there is no ‘isolated switch statement’, there is ‘selection statement’, ‘switch’ is one of them” and “all the selection statement have a uniform representation”.

2. “Gorillas” hide in everywhere.

The “invisible gorilla” refers to a famous experiment in psychology that vividly demonstrated how our focusing target can blind us to unexpected possibilities.

In the very first lesson of programming we had quickly realized that the switch statement is used to achieve multiple purposes in handling/dispatching. Since then the concept of “multi-purpose” was solidified and burnt to our brains. We see the switch is always followed by a pair of braces because “multi-purpose” means a bunch of statements are needed to get into different routines, thus it is.

But we never, or hardly question the assumptions, look beyond the familiar fact:

  • Is the switch statement designed to be with a brace?
  • Why are there other statements not mandatory to be bound with braces?
  • Does a “single-purpose” switch statement even make sense?

You cannot uncover blind spots without maintaining strong curiosity and scrutinizing beyond conventional thinking.

Conclusion

At the beginning, I was just looking for an elegant way simulating labeled break to replace gotos in my code. I eventually discovered that the familiar switch structure has some obvious, yet no one noticed syntax and features, hiding behind the specification of the standards.

Even seemingly simple constructs we use routinely may conceal powerful capabilities, awaiting those willing to question the invisible gorillas obstructing our enlightenment.

By embracing the lessons from this journey, I believe it can help us to become not only better programmers but also more insightful thinkers.