9

I've ran across code that looks like this:

  switch(i) {
    case 2: {
      std::cout << "2";
      break;
    case 3:
      std::cout << "3";
      break;
    }
    case 4: {
      std::cout << "4";
      break;
    }    
  }

Notice that case 2 opens a block with curly brace, which is closed only after case 3. At first this seemed like a typo that would either cause a compiler error, or, even worse, ignore case 3. But it works just fine in c++ and outputs 3 if i is 3. I come from java background so my understanding of logical blocks in c++ may be lacking. So my question is: is this deliberate behavior?

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68
  • 1
    The inner braces are modestly pointless but do no harm. There are worse things you can do with case labels than that. – Jonathan Leffler Feb 06 '17 at 22:44
  • 7
    `switch` statements in C and C++ are not as _logic_ as you may think. See the [Duff's device](https://en.wikipedia.org/wiki/Duff%27s_device) for an extreme example. – rodrigo Feb 06 '17 at 22:49
  • 1
    The cases are scoped by the switch - nothing else. One of many reasons not to use them. –  Feb 06 '17 at 22:49
  • It could have scoped a variable, but no variables in sight here. – user4581301 Feb 06 '17 at 23:00
  • @NeilButterworth, not to use **what** exactly? – SergeyA Feb 06 '17 at 23:02
  • @Serg switches, in general –  Feb 06 '17 at 23:03
  • 6
    @NeilButterworth, that is a far-reaching statement, with which I can not agree. – SergeyA Feb 06 '17 at 23:03
  • @Serg Fair enough, but it is my firmly held opinion. The overuse of switch-based programming is a blight, and those that think that switches will magically speed up their code are deluded. But from your answer here I guess you are happy with goto-style programming? –  Feb 06 '17 at 23:05
  • 2
    @NeilButterworth, switches, when used reasonably, provide good readability (as compared with long list of `if...else if...` statements) and, in some cases, provided optimization benefits. The former benefit outweights the latter. – SergeyA Feb 06 '17 at 23:08
  • @Serg We disagree - lets leave it at that. –  Feb 06 '17 at 23:10
  • 2
    @NeilButterworth Actually, there's no "magic" to the belief that `switch` blocks make faster code on average, just simple logic. A `switch` block does one check and one or two jumps (jump to `case`, jump out at `break` (if present)), while an unoptimised `if..else if..else` chain does one check & jump (either to `else`, or to the end of the chain) per `if` executed (meaning that for an `if..else if..else if..else` block, it does 1-3 checks & jumps). For any block that can be written as either, the `switch` would thus be slightly more efficient on average. – Justin Time - Reinstate Monica Feb 06 '17 at 23:48
  • [For any block that can be written as either, though, a smart compiler could just optimise it to a jump table anyways if you let it, so it'd still be a `switch` block after compilation. Haven't tested whether any actually _do_ this, however.] – Justin Time - Reinstate Monica Feb 06 '17 at 23:48
  • 2
    A switch block is a strong hint to an optimizing compiler that it should optimize it to a jump table. A series of if statements is a strong hint that it should not. I have seen cases where a compiler will transform a series of if statements into a jump table, @Justin, but the vast majority of the time, it will not. The assumption is that you will write what you mean. If there are more cases than would be readable for a series of if statements, then you'd write it as a switch-case, and it should be compiled as a jump table. Otherwise, the overhead of a jump table is a performance loss. – Cody Gray - on strike Feb 07 '17 at 01:37
  • @CodyGray Oh, that's interesting to know. I thought a jump table would be one of the most efficient ways to handle that kind of situation, since it should logically take fewer instructions. Didn't know it could have more overhead than a series of `if`s. – Justin Time - Reinstate Monica Feb 07 '17 at 01:49
  • [How can Duff's device code be compiled?](http://stackoverflow.com/q/5569416/995714), [How does Duff's device work?](http://stackoverflow.com/q/514118/995714) – phuclv Feb 07 '17 at 02:06
  • @CodyGray: Compilers should not use otherwise-equivalent language constructs as hints for making optimization decisions. If they do, the inevitable outcome is that programmers write not the form that makes the most sense for their program's logic, but the one that the compiler generates faster code for. An ideal optimizing compiler reduces all flow control to ifs and gotos before doing any optimization. – R.. GitHub STOP HELPING ICE Feb 07 '17 at 04:47
  • Well, I won't argue about the theory or what an "ideal" optimizer will do. I was just speaking about what happens *in practice*. In this case, though, I don't think it's particularly crazy for the reasons I already explained, even if that is the inevitable outcome. A jump table won't take fewer instructions than it would take to implement a simple if/else, and you also have locality to think about. Too much to expand upon in a comment. – Cody Gray - on strike Feb 07 '17 at 10:19
  • 1
    @CodyGray: A jump table is at best 1 instruction (if the input range is already tightly bounded by range analysis) and only a couple more if an initial range check is needed. I'm assuming you also want to count the table size, but if the possible cases are dense in the range the table size is likely smaller than the if/else tree. – R.. GitHub STOP HELPING ICE Feb 07 '17 at 16:48

2 Answers2

9

You can, but shouldn't, abuse case labels in a switch far worse than that — and far worse than Duff's Device. Duff's Device has the dubious privilege of being almost plausibly useful and yet can be regarded as abusing switch.

Not all abuses of switch can claim to be plausibly useful. For instance, this compiles as C or C++, even with stringent warnings set:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char **argv)
{
    unsigned seed;
    if (argc == 2)
        seed = atoi(argv[1]);
    else
        seed = time(0);
    printf("seed: %u\n", seed);
    srand(seed);

    int i = rand() % 10;
    int j = 21;
    int k = 37;
    printf("i: %d\n", i);
    switch (i)
    {
    case 1:
        for (j = 10; j > i; j--)
        {
        case 2:
            printf("case 2:\n");
            for (k = j - 1; k > 0; k--)
            {
            case 6:
                printf("case 6:\n");
            default:
                printf("%d-%d-%d\n", i, j, k);
            }
        case 5:
            printf("case 5:\n");
            printf("%d-%d\n", i, j);
            break;
        }
        break;
    case 3:
        printf("case 3:\n");
        break;
    }
    return 0;
}

The argument handling allows you set the seed, so you can reproduce results if you want to. I'd make no claim that it is useful; indeed, it is not useful. Note that break inside the loops breaks the loop, not the switch.

Basically, case labels (and default) must be within the scope of a switch, and are associated with the innermost enclosing switch. There are few other constraints on them. You have to be careful not to jump over variable initializations, etc (that's why j and k are defined outside the switch()). But otherwise, they're just labels, and control will flow to them when 'appropriate'.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
6

switch statement in C/C++ is a glorified goto statement (with some optimization benefits).

As a result, you can do as much with case labels as you'd do with goto labels. In particular, jumping inside the block is allowed, as long as you are not bypassing any variable initialization.

SergeyA
  • 61,605
  • 5
  • 78
  • 137