Search code examples
c++iteratorpost-incrementpre-increment

behavior of --it and it-- in


What is the explanation for the behavior of it++ and ++it in the context of a function call--and more specifically, for the function iter_swap? It confuses me that call 2 and call 4 in the code below appear to swap the elements, whereas call 3 and call 5 fail to swap elements. I expected the opposite result, however, I was assuming that one of the entries to the function parameters would execute before the other. Looking at the output, this appears not to be the case.

So, out of curiosity, is this behavior defined? How can I make sense of the order in which things execute? Thanks!

#include <iostream>
#include <vector>
#include <algorithm>
int main(void) {
    vector<int> a;
    a.push_back(1);
    a.push_back(2);

    vector<int>::iterator it_a, it_b;
    it_a = a.begin();
    it_b = it_a + 1;

    cout << *it_a << " " << *it_b << endl;
    // call 1
    iter_swap(it_a, it_b);
    cout << *it_a << " " << *it_b << endl;
    // call 2
    iter_swap(it_a, it_a++);
    cout << *--it_a << " " << *it_b << endl;
    // call 3
    iter_swap(it_a, ++it_a);
    cout << *--it_a << " " << *it_b << endl;
    // call 4
    iter_swap(it_a++, it_a);
    cout << *--it_a << " " << *it_b << endl;
    // call 5
    iter_swap(++it_a, it_a);
    cout << *--it_a << " " << *it_b << endl;

    return 0;
}

outputs:

1 2
2 1
1 2
1 2
2 1
2 1

Solution

  • Most of these examples don't have defined behaviour (call 1 is the sole exception). The evaluation of function arguments is unsequenced, meaning that their order of evaluation is unspecified, so whether the side effect of operator++ has kicked in on the other argument by the time the function is called is undefined. With a different compiler, you could get a different result, and both would be perfectly standard-conforming.

    Addendum: Some explanation of the semantics is in order, I believe.

    Before C++11, I would have been talking about sequence points here, but the standard language has changed to be clearer without changing very much. So instead I'll be talking about sequenced operations.

    Generally, operations in C++11 are partially ordered in time. That is to say, two operations (let's call them O and P) can be sequenced so that O is sequenced before P, that O is sequenced after P, or that O and P are indeterminately sequenced, or that O and P are unsequenced.

    The first two are straightforward: If O is sequenced before P, all its effects must have happened by the time P is evaluated, if O is sequenced after P, all effects of P have appeared when O comes around.

    As for the other two: If O and P are indeterminately sequenced, then either all effects of O appear before P is evaluated, or all effects of P appear before O is evaluated. If they are unsequenced, it's a great free-for-all: some effects of O may appear before some effects of P and at the same time some effects of P may appear before all effects of O have appeared -- in effect, the evaluation of O and P may overlap.

    This has to be seen in the context of optimization: optimizing compilers love to rearrange code to make it run faster. Partial specifications in terms of sequencing allow them to do much more of that -- perhaps O has an intermediate result that is useful for the evaluation of P, that sort of thing. If you consider, for example, a large arithmetic expression, it's not difficult to see how lax sequencing may help with vectorization and in the presence of a limited number of registers.

    So, where do your examples fall?

    In C++11, section 1.9 (15), it is mentioned that "value computations and side effects associated with different argument expressions are unsequenced." So you're in the deep end here, swimming with dragons.