Elegance or Trickery: Follow-Up
I think my Elegance or Trickery? post has received more comments than anything else I've written for Dr. Dobb's, so I'm going to stay on this topic for a while.
One of my favorite comments was that writing a.erase(iter++) hides a subtlety that is necessary for the code to work correctly. In particular, it is tempting to think that
a.erase(iter++);
has the same effect as
a.erase(iter); ++iter;
and it doesn't. The difference is that the first form increments iter before calling erase; the second increments iter afterward. Because erase invalidates iter, the second form doesn't work.
This problem is an example of a subtlety that lies behind many bugs, namely an expression that combines more than one side effect, or if its operation depends on when a side effect happens. In this particular example, erase has the side effect of invalidating iter, and iter++ and ++iter both rely on iter having a particular valid value. Therefore, for the statement to work correctly, erase's side effect has to happen after the program has incremented iter.
The side effects happen in the proper sequence in the first example, by virtue of the fact that a function's arguments are evaluated completely before the function is executed. In the second example, they are forced to happen the wrong order: First, iter is invalidated, and only then is its (now invalid) value used.
The trickery in the first example comes from the fact that an essential condition (ordering of side effects) is hidden in the code: Readers are not used to code that relies on arguments' side effects happening before the function executes. But what is the source of its elegance?
I think the answer is that it manages to achieve its effect without defining an extra variable. Remember the alternative that I presented? I can abbreviate it this way:
auto next = iter; ++next; a.erase(iter);
This code appeared in a loop that would normally have incremented iter at the end of the loop body. Because iter is now invalid, I replaced the increment by
iter = next;
I dislike temporary variables, but on deeper reflection, what I really dislike about this code is that the temporary variable next persists through the entire loop body. This persistence means that all of the code between when next is created and when it is finally copied to iter might accidentally modify next, a possibility that makes the code that much harder to maintain.
For this reason, I would like to offer yet another alternative:
{ auto it = iter;
++iter;
a.erase(it); }
The difference between this code and the earlier version is that it increments iter immediately. In that sense, it really does have the same effect as a.erase(iter++). Moreover, the curly braces limit the scope of the temporary variable it, so that we do not have to worry that the rest of the code might change the value of it.
I invite comments: Is this version better than the others? Does it qualify as elegant? Or is it still too tricky?

