Notice that precedence and associativity tells how the expression tree looks (except for the "contractions" that Mate posted about).
But they do not tell in which order the branches in the tree are evaluated.
The link you posted is perfectly true, Nick. But it only talks about precedence and associativity.
Parenthesis are used to overide the operators precedence and associativity, but do not change evaluation order. (Other than the obvious one that subexpressions must be evaluated before the operator who works on them.)
So no amount of parenthesises will help that expressions with side effects are undefined if other parts of the same expression depends on that side effect.
Nick said:
That being said, is (i++) + (i++) well defined?
No.
I assume that you thought that if i starts as 2, then that expression could be 2+3 or 3+2, both equal to 5.
But IIRC it is a bit worse than that. The increment must be done before the ending semicolon of the statement, so a compiler has the right to do this.
Code:
// Real code:
j = (i++) + (i++);
// Calculate as:
j = i + i; // Or 2*i
i += 2;
Same thing with pre-increment. It must be done somewhere between the end of last statement (semicolon or equivalent), and when the value is used.
But this undefined evaluation order is usually no big problem, as far as you know it.
The coding rule is simply that if you do an i++ or ++i in an expression, never reference i at any other place in the same expression. If you just stick to that, everything works as you always thought it did. (Again except for the contractions.)
The only problem is the contractions which could hurt numerically critical code real bad. A good thing that they added a "#pragma fp_contract off".
[Edit]
Btw, I could connect this to the coding style thread by saying that you shouldn't use i++ and other uses of i in the same expression anyway. It's usually not a readable coding style.