Some kind of modified switch()/case construct for C?



  • Hello everybody,

    in one of my programs I have the need to do some actions based on some independent decisions.
    One of my tries was something like this:

    if (a) DoA();
    if (a || b) DoB();
    if (a || b || c) DoC();
    

    but that was unreadable, especially as there were expressions instead of a, b and c.

    What I'm currently using is

    if (a) goto label_a;
    if (b) goto label_b;
    if (c) goto label_c;
    goto end;
    

    label_a: DoA();
    label_b: DoB()
    label_c: DoC()
    end:

    but that's not beautiful either.

    Is there some cleaner construct for a switch/case in C, that allows arbitrary expressions (and fall-through)? Somethink like (cond) in LISP?

    Thank you for all answers.



  • @flop said:

    if (a) goto label_a;
    if (b) goto label_b;
    if (c) goto label_c;
    goto end;

    label_a: DoA();
    label_b: DoB()
    label_c: DoC()
    end:

    IMHO, nothing wrong with this approach; after all, it's how switch works.



  • You could evaluate your expressions to bools in advance and use them in the (a || b || c) stuff. C99 supports bool data type if you #include <stdbool.h>- see http://en.wikipedia.org/wiki/Boolean_datatype#C99

    Or if the code is in functions, you could do

    if (expr_a)
    {
        DoA();
        DoB();
        DoC();
    }
    else if (expr_b)
    {
        DoB();
        DoC();
    }
    else if (expr_c)
    {
        DoC();
    }


  • Actually, I think the three if statements at the top are the most understandable. I think that the intent is very obvious and, when you're looking at the code in 6 months, you won't have to spend any time thinking what it does.  Anyway, here's my solution - be warned, it's a little WTF-ish (but works!)


    #include <stdio.h><STDIO.H>
    

    int main() {

    int a, b, c;
    int flag_val;
    
    a = 0;
    b = 1;
    c = 0;
    
    if (a &gt; 0) a = 4;
    if (b &gt; 0) b = 2;
    
    flag_val = a + b + c;
    
    switch (flag_val) {
        case 4:case 5:case 6:case 7: printf("Label_a\n");
        case 2:case 3: printf("Label_b\n");
        case 1: printf("Label_c\n"); break;
        default: printf("Do Nothing.\n");
    }
    

    }



  • I would slightly modify DoctorFriday's code to make it simpler. 

    To make it more readable, I'd actually assign temporary variables to the conditions since you said that a, b, and c were complex expressions. This method also ensures that DoA, DoB, and DoC are executed in the same order as your original code, which may be important:

    t1 = /* expression a */;
    t2 = /* expression b */;
    t3 = /* expression c */;
    

    switch(t1?1:(t2?2:(t3?3:0) ) ) // parentheses are important for trinary operator!
    {
    case 1: DoA(); break;
    case 2: DoB(); break;
    case 3: DoC(); break;
    default: error(); break;
    }

    This is a little cleaner and faster as it eliminates all unnecessary comparisons.



  • Ah crud - caught an error after the edit deadline:  the break statements in my switch should not be there!  Fixed below:


    t1 = /* expression a */;
    t2 = /* expression b */;
    t3 = /* expression c */;

    switch(t1?1:(t2?2:(t3?3:0) ) ) // parentheses are important for trinary operator!
    {
    case 1: DoA();
    case 2: DoB();
    case 3: DoC(); break;
    default: error(); break;
    }


  • @too_many_usernames said:

    Ah crud - caught an error after the edit deadline:  the break statements in my switch should not be there!  Fixed below:


    t1 = /* expression a */;
    t2 = /* expression b */;
    t3 = /* expression c */;

    switch(t1?1:(t2?2:(t3?3:0) ) ) // parentheses are important for trinary operator!
    {
    case 1: DoA();
    case 2: DoB();
    case 3: DoC(); break;
    default: error(); break;
    }

    It's kind of amusing at what lengths people go to avoid using <font face="Courier New">goto</font>s. Not saying the code is bad, but note that this code is not equivalent to the <font face="Courier New">goto</font>ful one, because it evaluates all expressions, whereas the latter stops at the first true expression.

    Also, WTF is with the importance of the parentheses? They are definitely redundant in this case. (I also never heard of the ternary operator being referred to as "trinary", but Wiktionary says it's OK, so never mind.)



  • @Spectre said:

    It's kind of amusing at what lengths people go to avoid using <font face="Courier New">goto</font>s. Not saying the code is bad, but note that this code is not equivalent to the <font face="Courier New">goto</font>ful one, because it evaluates all expressions, whereas the latter stops at the first true expression.

    Also, WTF is with the importance of the parentheses? They are definitely redundant in this case. (I also never heard of the ternary operator being referred to as "trinary", but Wiktionary says it's OK, so never mind.)

     

    I actualy don't have a problem with <font face="Courier New">goto </font>personally; I don't understand why people think it's bad (it's all <font face="Courier New">goto </font>at the CPU level anyway). (A worse offense in my book is <font face="Courier New">return</font>ing from the middle of a subroutine instead of in one place at the end - *that* is a debugging nightmare.)  You can also avoid gotos and unecessary expression evaluation if you are willing to duplicate a small amount of code, but I guess in the end it depends on personal preference:

    if(a){DoA();DoB();DoC();} else if(b) { DoB();DoC(); } else if (c) { DoC(); }


  • @too_many_usernames said:

    @Spectre said:

    It's kind of amusing at what lengths people go to avoid using <font face="Courier New">goto</font>s. Not saying the code is bad, but note that this code is not equivalent to the <font face="Courier New">goto</font>ful one, because it evaluates all expressions, whereas the latter stops at the first true expression.

    Also, WTF is with the importance of the parentheses? They are definitely redundant in this case. (I also never heard of the ternary operator being referred to as "trinary", but Wiktionary says it's OK, so never mind.)

     

    I actualy don't have a problem with <font face="Courier New">goto </font>personally; I don't understand why people think it's bad (it's all <font face="Courier New">goto </font>at the CPU level anyway). (A worse offense in my book is <font face="Courier New">return</font>ing from the middle of a subroutine instead of in one place at the end - *that* is a debugging nightmare.)  You can also avoid gotos and unecessary expression evaluation if you are willing to duplicate a small amount of code, but I guess in the end it depends on personal preference:

    if(a){DoA();DoB();DoC();} else if(b) { DoB();DoC(); } else if (c) { DoC(); }

    Your arguments lack any rational base.  Yes, it's "all gotos at the CPU level".  It's also all binary arithmetic.  I'm pretty sure neither is particularly conducive to development by humans.  That doesn't invalidate your statement, but it does mean your argument is garbage.  As far as returning from multiple points in a function: it is far messier to go to all the trouble to ensure that your function only returns at the end.  I find that people who believe in such "universal rules" also tend to write horribly unmaintainable code that ends up with several bugs after release that are fixed in such a sloppy manner as to introduce further bugs, etc..  A generalization, yes, but one I have found to be quite accurate.  It seems that half of the awful mistakes in software products are produced by otherwise intelligent developers who insist on arbitrary, superstitious practices as an alternative to engaging their brain.



  • Maybe

    Boolean temp = false;

    temp |= a;
    if (temp)
      DoA();

    temp |= b;
    if (temp)
      DoB();

    temp |= c;
    if (temp)
      DoC();

    would be more readable than the previous switch solutions



  • @Resistance said:

    Maybe

    Boolean temp = false;

    temp |= a;
    if (temp)
      DoA();

    temp |= b;
    if (temp)
      DoB();

    temp |= c;
    if (temp)
      DoC();

    would be more readable than the previous switch solutions

    Maybe, but what the fuck does it mean?


  • @upsidedowncreature said:

    Maybe, but what the fuck does it mean?

    I'm just wondering if you are serious. Using bitwise operators usually doesn't yield much clarity, but this construct is essentially equivalent to a weaved-through version of the ifelse chain mentioned earlier.



  •  @upsidedowncreature said:

    @Resistance said:

    Maybe

    Boolean temp = false;

    temp |= a;
    if (temp)
      DoA();

    temp |= b;
    if (temp)
      DoB();

    temp |= c;
    if (temp)
      DoC();

    would be more readable than the previous switch solutions

    Maybe, but what the fuck does it mean?

    It's incrementally finding the value of a || b || ..., adding one operand each time (x |= y is same as x = x || y)



  • @Resistance said:

    It's incrementally finding the value of a || b || ..., adding one operand each time (x |= y is same as x = x || y)

    Ah.  Thanks.  My face is red, and I've resolved not to post in future when I'm into the second bottle of wine.



  • @too_many_usernames said:

    <p 

    (A worse offense in my book is <font face="Courier New">return</font>ing from the middle of a subroutine instead of in one place at the end - that is a debugging nightmare.)

    Really? I hope i don't end up debugging your code, what's the alternative, massive if statements? pointless break statements etc.? Although i guess it depends what language you're writing it in but in c#, java etc. you're going to end up with a dogs breakfast.
    Also how does it make it difficult to debug?



  • [code]

    doA = a;
    doB = doA || b; // Or "= a || b" if expression a has side effects
    doC = doB || c; // Or "= a || b || c" if expressions a or b have side effects

    if (doA) DoA();
    if (doB) DoB();
    if (doC) DoC();

    [/code]




  •  The original code you wrote was fine.



  •  @chebrock said:

     The original code you wrote was fine.

     

    If a, b, and c are very simple expressions I would agree, but once they get beyond a few terms I think some reorganisation would improve readbility and maintainability.

    But not something to lose too much sleep over, for sure.




  • @Hatshepsut said:

    If a, b, and c are very simple expressions
     

     If that's not the case then assign the expressions to variables and then use the same conditional.



  • @chebrock said:

    @Hatshepsut said:

    If a, b, and c are very simple expressions
     

     If that's not the case then assign the expressions to variables and then use the same conditional.

     

    What if the expressions include side effects?



  • Side effects? 


    doit = expression_a;

    if (doit) doA(); 

    if (!doit) doit = expression_b;

    if (doit) doB();

    if (!doit) doit = expression_c;

    if (doit) doC();

     

    Same semantics as the original, not terribly elegant, but quite straightforward. 



  • @Ilya Ehrenburg said:

    Side effects? 


    doit = expression_a;

    if (doit) doA(); 

    if (!doit) doit = expression_b;

    if (doit) doB();

    if (!doit) doit = expression_c;

    if (doit) doC();

     

    Same semantics as the original, not terribly elegant, but quite straightforward. 

     

     

    Which raises the interesting pont that the OP's second version doesn't have the same semantics as the first if doA() or doB() have side effects that affect expressions a, b or c...




  • Typically if I see these type of things (lots of 'unrelated' related decisions) there must be something wrong. It's like it has multiple entry points..

    If suitable/possible, move the DoA, DoB and DoC stuff to actual procedures, then

    void DoA( bool Execute) {

      if (Execute) {

      ....stuff

      }

    DoA( a);

    DoB( a || b);

    DoC( a || b || c);

    ?

     

    Of course, DoA could also call DoB, and DoB could call DoC...

    hence

    if (a) DoA else if (b) DoB else if (c) DoC....

     And another, make them functions returning a bool:

    DoC( DoB( DoA(a) || b) || c);

    How to make a mess out of a few simple statements. lol.

     

     



  •  Final verdict: the goto statement solution wins.



  • switch(true){ ... }



  • @zzo38 said:

    switch(true){ ... }
     

    Um, the OP specified that the language is C, and arbitrary expressions are required.  In C, case labels have to be constant integral expressions.



  • @CodeSimian said:

    @zzo38 said:

    switch(true){ ... }
     

    Um, the OP specified that the language is C, and arbitrary expressions are required.  In C, case labels have to be constant integral expressions.

    O, sorry I forgot that I never used that in C, but I did that in other program languages, but I just looked it up now, you are correct, however GCC support ranges of case such as: case 'A' ... 'Z' : but thanks for telling me that OK

    The way with goto label is best way. Some people say GOTO command is bad and should never be used but I think it should be used sometimes, but not all the time.



  • I like the goto way pretty much. IMHO gotos are mostly harmful when used to fake a loop, but your way seems pretty clean to me. It's not like you're not going to understand what it does in 5 months from now.



  • Until someone has to add a DoE which only should run when B or D are true.....


Log in to reply