Ternary operator ?

Discussion in 'Mac Programming' started by larswik, Jul 29, 2015.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    I have a simple method I created to swap 2 images. I am trying to reduce the code down from the if statement to a ternary operator. The if statment functions as expected but for some reason when I comment out the If statement the ternary will not increment the value, and always has the value of 1. The m1 variable is a global int.

    As I understand it, the condition in the ternary is evaluated first and then assigned back to the variable. I must be over looking something simple here.

    Code:
    -(IBAction)monthOneButton:(id)sender{
      
        m1 = (m1 < 2)? m1++ : 1;
        d1.image = [numImageArray objectAtIndex:m1];
       
        // Below is the old way, above is what I want.
       
        /*
        if (m1 == 1) {
            d1.image = [numImageArray objectAtIndex:2];
            m1 = 2;
        }
        else{
             d1.image = [numImageArray objectAtIndex:1];
            m1 = 1;
        }
         */
       
    }
     
  2. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #2
    You understand the ternary operator correctly. However, you are using post-increment on m1 (i.e. m1++). This increments m1 (from 1 to 2), but then returns the original value. Stripping out the ternary operator, this:

    Code:
    m1 = m1++;
    
    is equivalent to

    Code:
    int temp=m1;
    m1=m1+1; // m1++
    m1=temp; // m1=...
    
    You can use pre-increment (i.e. ++m1), which increments m1 and then returns the new value. But that feels a little odd:

    Code:
    m1 = ++m1;
    
    is equivalent to

    Code:
    m1=m1+1; // ++m1
    m1=m1; // m1=...
    
    The compiler will optimize that out so it isn't a problem, it just feels odd to me. I'd suggest just using m1+1 in that clause, since that is clearer.

    Edit: If your values are only ever 1 or 2, you could just set it directly to 2 instead... i.e.
    m1 = (m1 < 2)? 2 : 1;
     
  3. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #3
    But you use post increment on the m1 variable, so it will be assigned first, then incremented.
     
  4. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #4
    This makes a lot of sense to me, and prevents unexpected values of m1 if 1 and 2 are the only choices. E.g. Would you want it to return 1 if m1 starts at 0?

    B
     
  5. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #5
    Thanks guys, now I see the problem. The 2:1 will work here, but my values will be 0-9 for other methods which is why I wanted to increment. m1++; is now m1 + 1;

    Thanks!
     
  6. gnasher729 macrumors P6

    gnasher729

    Joined:
    Nov 25, 2005
    #6
    m1 = (m1 < 2)? m1++ : 1;

    is (most likely) undefined behaviour, because m1 is modified twice - at the least you don't know whether the ++ is performed before or after the assignment to m1, but the compiler is free to totally mess up your program because of the undefined behaviour.
     
  7. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #7
    Good catch. m1=m1++; is certainly undefined behavior in C/C++ (gotta love things that are defined as undefined!). I'm not so sure about this case though because the ternary operator does introduce a sequence point... Ah, but it is immediately after evaluating the conditional, so I think you are right, the assign and increment is still UB since there is no sequence point between them (that is, both occur after the sequence point the ternary created).
     
  8. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #8
    It's an interesting question, but it seems like the conditional operands are only evaluated if the condition is true. So in that regard it should be equivalent to an if/then statement. From the standard section 6.5.15

    But then comes a bit that could be relevant.

    In this case an attempt is made to modify the result no? And why would it matter if (m1 + 1) or (++m1) is used if that's the case?
     
  9. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #9

    m1+1 should be safe: that doesn't modify m1 or the result of the ternary. m1 is only modified once by the assignment. Basically, m1=m1+1; is safe (or at least, it had better be!). And the reult of the ternary will be the value of m1+1 (let's say that that happens to be 2). That value (2) is then never modified. So no UB there, even though there were two chances to find some.



    Pre-increment is interesting, though. I suspect (but am unsure) it is technically UB but all the obvious ways to implement it have the same result; it seems to me that a compiler would have to be deliberately malicious to find a way to generate code for m1=++m1; that doesn't work as expected. Though I guess you should never bet against that possibility... But it still looks bizarre and is something I would avoid regardless of the UB. Though I think this is UB only because of the assignment and modification of m1. The result of the ternary is not modified or accessed past the last sequence point (the result is the value of ++m1, which again is just a number, like 2, that is never modified). The UB is because the incr hasn't necessarily updated the actual value of m1 yet by the time the assign happens.

    At least I think. I'm not an expert.
     
  10. subsonix, Jul 30, 2015
    Last edited: Jul 30, 2015

    subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #10
    Well, I think I'm mistaken because the result is which ever operand is evaluated depending on the condition. But as far as I can tell, both m1 + 1 and ++m1 should evaluate to m1 + 1, it's just m1++ that evaluates to m1. But I too generally avoid using both postfix and prefix operators when there are side effects.

    Edit:

     
  11. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #11
    Agreed. I think in this case the ternary itself is safe, so lets ignore it and focus just on m1=++m1; In this case, what I do not know is if the spec requires that the value of m1 in RAM actually be modified immediately, or if it can be deferred slightly. The computer obviously has to compute m1+1 in any event, and stash that in a register, but I don't know if it must immediately push that off to RAM or if it can wait. Lets just say that m1 was initially 1. In this case, the compiler will need to plant code to write the value of 2 back to m1 in RAM twice, and the order of those might be undefined. Obviously, though, since both writes are 2, it really doesn't matter. (i.e. it doesn't matter if it writes 2 before 2, because they are the same)

    But consider a similar case: m1=++m1+1; If m1 is initially 1, now there might be overlapping writes of 2 (++m1) and 3 (m1=...) being sent to RAM. Since there are no sequence points involved, the order of those two writes is probably undefined behavior (unless the spec defines that ++m1 is atomic, which basically requires it to have a sequence point). In this case, if matters if it decides to write the 2 before the 3 or vice versa.

    And even in the first case, where the UB is only on what order two equivalent writes happen, I still am cautions. In the face of undefined behavior, a compiler COULD plant code to format your hard drive (or do anything else it wants) without violating the spec. It won't, but it could. So, just because it is harmless in all the obvious implementations doesn't mean it always will be. (I always think that it would be amusing to write a compiler with a --hard-mode switch to enable format-hard-drive-on-undefined-behavior. Horrifying, but amusing if used in a sandbox/VM just to see how much UB we actually encounter.)
     
  12. subsonix, Jul 30, 2015
    Last edited: Jul 30, 2015

    subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #12
    I agree that you shouldn't depend on UB. But in this case both ++m1 and m1 + 1 are equivalent and evaluate to m1 += 1. It's required to by the standard. The same whould be true for ++m1+1, that expression evaluates to (m1 + 1 + 1). It's only m1++ that applies to UB, since it modifies the value after it's been evaluated, which is undefined behaviour for the ternary operator. I don't think you need to think about what the compiler does, it's the result that matters as there could be many different implementations on different CPUs and compilers. It's the entire point of the C abstract machine and undefined behaviour to begin with.
     
  13. dylanryan macrumors member

    Joined:
    Aug 21, 2011
    #13
    Ah, you're right. ++m1 is equivalent to (m1+=1) which is equivalent to (m1=m1+1), and that equals sign does create a sequence point, and the added parens ensure that it is evaluated before the rest, which means that it has to be fully evaluated before other things on the line can happen.

    So... Safe but suspicious-looking enough to avoid, if only so that you don't have to double-check the spec every time you look at the code!
     
  14. theluggage macrumors 68030

    Joined:
    Jul 29, 2011
    #14
    More to the point forget the language lawyer stuff,

    Code:
    m1 = (m1 < 2) ? ++m1 : 1;
    ...is just unnecessarily obtuse, as illustrated by the fact that the original poster used the wrong operator the first time and couldn't spot the mistake.

    Code:
    m1 = (m1 < 2) ? m1 + 1 : 1;
    Is much clearer - leave it up to the compiler to optimise the +1 to an increment, not that a button click handler needs much optimising!
     
  15. zeppenwolf macrumors regular

    zeppenwolf

    Joined:
    Nov 17, 2009
    #15
    Code:
    m1 < 2 ? m1++ : m1 = 1;
    Meets OP's needs, is as clear as it gets, and no need to even care whether we use postfix or prefix increment operator.
     

Share This Page