Search code examples
c++stlcontainersundefined-behaviorerase

Is the sequencing of this decrement invoking undefined behaviour?


I am looking for confirmation, clarification is this code well defined or not.

It is very common to erase elements of a container in a loop by re-assigning the iterator to the result of the container's erase() function. The loop is usually a little messy like this:

struct peer { int i; peer(int i): i(i) {} };

int main()
{
    std::list<peer> peers {0, 1, 2, 3, 4, 5, 6};

    for(auto p = peers.begin(); p != peers.end();) // remember not to increment
    {
        if(p->i > 1 && p->i < 4)
            p = peers.erase(p);
        else
            ++p; // remember to increment
    }

    for(auto&& peer: peers)
        std::cout << peer.i << ' ';
    std::cout << '\n';
}

Output: 0 1 4 5 6

It occurred to me that the following might be little tidier, assuming it is not invoking undefined behavior:

struct peer { int i; peer(int i): i(i) {} };

int main()
{
    std::list<peer> peers {0, 1, 2, 3, 4, 5, 6};

    for(auto p = peers.begin(); p != peers.end(); ++p)
        if(p->i > 1 && p->i < 4)
            --(p = peers.erase(p)); // idiomatic decrement ???

    for(auto&& peer: peers)
        std::cout << peer.i << ' ';
    std::cout << '\n';
}

Output: 0 1 4 5 6

The reasons why I think this works are as follows:

  • peers.erase() will always return an incremented p, therefore it is safe to decrement it again

  • peers.erase(p) makes a copy of p so it will not operate on the wrong value due to sequencing reference

  • p = peers.erase(p) returns a p& so the decrement operates on the correct object reference.

But I have niggling doubts. I am worried I am invoking the bad sequencing rule by using --(p) in the same expression where p is used as a parameter despite the fact that it looks on paper to be okay.

Can anyone see a problem with my assessment here? Or is this well defined?


Solution

  • It depends on the condition that you are using to detect those elements to delete. It will fail if you try to delete the first element as erase will return the new begin() and you are then decrementing it. This is illegal, even if you immediately increment it again.

    To avoid this error and because it is more common and readable, I'd stick with the first version.