8

I have this code in a section of my project:

enum myEnum
{
    invalid = -1,
    val1 = 1,
    val2 = 2,
    val3 = 4
};

int bitmask = val1 | val3;

if(bitmask & val1)
    ...
if(bitmask & val2)
    ...
if(bitmask & val3)
    ...

This is fine, and it works perfectly, but i've always wondered if it could be done with a switch. I was thinking something along the lines this:

int checkMask(int& mask)
{
    for(int i = 0; mask; mask &= ~(1 << i++))
    {
        if(mask & (1 << i))
        {
            int ret = mask & (1 << i);
            mask &= ~ret;
            return ret;
        }
    }

    return invalid;
}

#define START_BITMASK_SWITCH(x) int xcopy = x; while(xcopy) { switch(checkMask(xcopy))
#define END_BITMASK_SWITCH };

int bitmask = val1 | val3;

START_BITMASK_SWITCH(bitmask)
{
    case val1:
        ...
        break;
    case val2:
        ...
        break;
    case val3:
        ...
        break;
}
END_BITMASK_SWITCH

so my questions are:

  • have i just solved my problem? i suppose i have, but is it a clean solution?
  • is there a simpler way of accomplishing this?
  • is it a bad idea to mix #defines and functions?
  • Tom
    • 908
    • 1
    • 6
    • 14

    7 Answers7

    14

    No it's not a clean solution and for your context, you can avoid mixing #define and functions. You can try below solution, if you want switch():

    int bitmask = val1 | val3;
    int mask = 1;
    while(bitmask)
    {
      switch(bitmask & mask)
      {
      case val1: ... break;
      case val2: ... break;
      case val4: ... break;
      case val8: ... break;
      }
      bitmask &= ~mask; 
      mask <<= 1;
    }
    
    iammilind
    • 68,093
    • 33
    • 169
    • 336
    8

    No, it is (obviously) not a clean solution. Your original code was straight-forward, didn't loop, and didn't involve special-case "secret" macros that add weird constructs to the language.

    By "weird construct", I meant the START_BITMASK_SWITCH()/END_BITMASK_SWITCH macros, which:

    • Add a loop without using any of the standard keywords to even hint that a loop is happening
    • Clobber names in the current scope more or less silently
    • Include a spurious semi-colon

    There's no benefit to your solution, all it does is add bloat and overhead (both in terms of code size, and complexity, and run-time performance) just to scratch that itch of for some reason wanting to use a switch to do something that it's not very well-suited to do.

    Obviously, this is highly subjective, but you did ask.

    unwind
    • 391,730
    • 64
    • 469
    • 606
    • fair comment, it was more of an exercise to see if it could be done. Could you elaborate on weird constructs? – Tom Jul 07 '11 at 08:19
    • START_BITMASK_SWITCH is a bad solution, but it doesn't preclude a good one. See my Flag/Position enum approach below. – spraff Jul 07 '11 at 08:30
    • I agree with unwind here, this is horrible - there is absolutely nothing to be gained from this approach aside from some scratching of the head a few months down the line when you discover a bug. – Nim Jul 07 '11 at 09:54
    • "If *it* could be done"? What's *it*? What was the pressing inconvenience caused by the original syntax? – Kerrek SB Jul 07 '11 at 10:03
    • what do you mean by clobber names? – Tom Jul 07 '11 at 13:51
    • I wouldn't say there's no benefit, getting a warning from `-Wswitch-enum` if not all bit mask values are checked could be desirable. It's a shame there doesn't appear to be a way to get the compile-time exhaustive checking with ideal performance in C-based languages. – Quinn Taylor Feb 18 '16 at 06:24
    • @Tom The start macro introduces the name `xcopy`, which isn't very unique-looking and might clobber ("shadow") an existing variable. Or, worse, fail if that variable is in the same scope due to trying to redeclare it. – unwind Feb 18 '16 at 09:10
    5

    I see several problems:

    • it adds preprocessor cruft with no real benefit
    • it adds a lot of slow code (shifts, loops, tests)
    • it prevents you from adding special cases such as "if bit 2 is on and bit 3 is off" (if ((bitmask & (val2 | val3)) == val2))
    • the compiler will miss almost every possibility to optimise the generated code

    It can also be done in a much, much simpler way:

    #define START_BITMASK_SWITCH(x) \
        for (uint64_t bit = 1; x >= bit; bit *= 2) if (x & bit) switch (bit)
    
    int bitmask = val1 | val3;
    
    START_BITMASK_SWITCH(bitmask)
    {
        case val1:
            ...
            break;
        case val2:
            ...
            break;
        case val3:
            ...
            break;
    }
    
    sam hocevar
    • 11,853
    • 5
    • 49
    • 68
    3

    A bitmask is just an array of bools if you want, and your enum are the indices. Can you switch over an array of bool? No you can't, because it can represent multiple states at the same time. You could only switch over the overall bitmask like with any integer.

    Xeo
    • 129,499
    • 52
    • 291
    • 397
    0

    Of course you can, if that's what you really want. As mentioned there are only limited cases, maybe none, where you may ever need to. But you can.

    #include <iostream>
    
    enum myEnum
    {
        invalid = -1,
        val1 = 1,
        val2 = 2,
        val3 = 4
    };
    
    int main()
    {
        const int bitmask = val1 | val3;
    
        switch (1) {
            case (bitmask & val1) : std::cout << "1"; break;
            case (bitmask & val2) : std::cout << "2"; break;
            case (bitmask & val3) : std::cout << "3"; break;
            default: break;
        }
    }
    
    • 1
      I realize this is old but if I'm understanding this correctly, I don't think it would work. `(bitmask & valx)` is only equal to 1 in the case of `val1`. `(bitmask & val3)`, for instance, is `4`, so a switch searching for value `1` wouldn't catch it. – TheTrueJard Apr 05 '20 at 19:33
    0

    You could make a foreach construct where you iterate through the bits of your bitmask and provide a function with a switch statement in it.

    duedl0r
    • 9,289
    • 3
    • 30
    • 45
    -1
    enum Positions {
        ALPHA,
        BETA,
        GAMMA
    };
    
    enum Flag {
        ALPHA_FLAG == 1 << ALPHA,
        BETA_FLAG  == 1 << BETA,
        GAMMA_FLAG == 1 << GAMMA
    };
    
    Position position_of (Flag f) {
        unsigned n = f;
        unsigned i = 0;
        while ((n & 1) == 0) {
             ++i;
             n >>= 1;
        }
        return Position (i);
    }
    
    switch (position_of (flag)) {
        case ALPHA:
        case BETA:
        // ...
    };
    

    This is nicer with C++0x strong enums, then you can have Position::ALPHA and Flag::ALPHA for clearer naming. You can also use constexpr to safely bitmask your Flag values.

    spraff
    • 32,570
    • 22
    • 121
    • 229