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