Yes, your first example has undefined behavior because we have an unsequenced modification and access of a memory location, which is undefined behavior. This is covered in draft C++ standard [intro.execution]p10 (emphasis mine):
Except where noted, evaluations of operands of individual operators
and of subexpressions of individual expressions are unsequenced.
[ Note: In an expression that is evaluated more than once during the
execution of a program, unsequenced and indeterminately sequenced
evaluations of its subexpressions need not be performed consistently
in different evaluations. — end note ] The value computations of the
operands of an operator are sequenced before the value computation of
the result of the operator. If a side effect on a memory location
([intro.memory]) is unsequenced relative to either another side effect
on the same memory location or a value computation using the value of
any object in the same memory location, and they are not potentially
concurrent ([intro.multithread]), the behavior is undefined. [ Note:
The next subclause imposes similar, but more complex restrictions on
potentially concurrent computations. — end note ] [ Example:
void g(int i) {
i = 7, i++, i++; // i becomes 9
i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined
i = i + 1; // the value of i is incremented
}
— end example ]
If we check out the section of relational operators which covers <=
[expr.rel] it does not specify an order of evaluation, so we are covered by intro.exection
and thus we have undefined behavior.
Having unspecified order of evaluation is not sufficient for undefined behavior as the example in Order of evaluation of assignment statement in C++ demonstrates.
Your second example avoids the undefined behavior since you are not introducing a side effect to ival
, you are just reading the memory location twice.
I believe that is a reasonable way to solve the problem, it is readable and not surprising. An alternate method could include introducing a second variable, e.g. index
and prev_index
. It is hard to come with a fast rule given such a small code snippet.