Skip to content

40 Intermediate The Hundred

The Erase That Skips

a loop that deletes every 2, more or less

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v{1, 2, 2, 3};

    for (std::size_t i = 0; i < v.size(); ++i)
        if (v[i] == 2)
            v.erase(v.begin() + i);   // delete every 2

    for (int x : v)
        std::cout << x << ' ';
    std::cout << '\n';
}

Run it. What does it print?

Answer

1 2 3 — one of the 2s survives. Every time, on every compiler.

Why

erase closes the gap: everything after the erased slot shifts left by one, so the next element slides into position i — and then ++i steps right over it, unexamined. With {1, 2, 2, 3}, erasing the 2 at index 1 moves the second 2 into index 1, and the loop never looks at index 1 again. Adjacent duplicates therefore survive in alternation. Note this is not undefined behavior — indices stay valid and the result is deterministic — which makes it nastier: no crash, no sanitizer, just quietly wrong data. The iterator-based sibling is worse: after c.erase(it), the iterator is invalidated and ++it is undefined behavior — the correct pattern is it = c.erase(it) (and ++it only when you don't erase).

The fix

Don't advance past an element you haven't examined — only ++i when you don't erase:

for (std::size_t i = 0; i < v.size(); )   // no ++i here
    if (v[i] == 2) v.erase(v.begin() + i);
    else           ++i;

Better still, say what you mean: C++20 added std::erase(v, 2) and std::erase_if, which do it in one line; pre-C++20, use the erase–remove idiom (entry 41).

Takeaway: erasing shifts the next element into the current slot — advance the index only when you didn't erase, or let std::erase do the whole job.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry