Skip to content

39 Intermediate The Hundred

push_back During the Loop

the to-do list that ate itself

#include <iostream>
#include <vector>

int main() {
    std::vector<int> tasks{10, 20, 30};
    std::cout << "size " << tasks.size() << ", capacity " << tasks.capacity() << "\n";

    for (int t : tasks) {
        std::cout << "handling " << t << "\n";
        if (t == 10)
            tasks.push_back(99);   // task 10 spawns a follow-up task
    }
}

Run it. Which tasks get handled?

Answer

Only 10. Typical output (GCC on x86-64): handling 10, handling 0, then handling 1258427999 — a different garbage number every run. Tasks 20, 30 and 99 never appear.

Why

A range-for is sugar for auto __b = tasks.begin(), __e = tasks.end(); — both captured once, before the body ever runs. {10, 20, 30} allocates exactly three slots (hence capacity 3), so the first push_back must reallocate: new buffer, elements moved over, old buffer freed. The loop's cached iterators still point into the freed block, and every dereference after that is undefined behavior — the 0 and the ever-changing number are the allocator's own bookkeeping, scribbled over the block as it was freed. With spare capacity the loop may even appear to work, but it is UB all the same: push_back always invalidates end(), the exact iterator the loop compares against. The same reallocation also snaps every pointer and reference into the vector — an int& first = tasks[0] taken before the push_back goes stale the same way. Neither -Wall -Wextra nor -O2 says a word: the compiler cannot see this one for you.

The fix

Iterate by index, with a snapshot of the size:

const std::size_t n = tasks.size();   // snapshot: walk only the originals
for (std::size_t i = 0; i < n; ++i) {
    std::cout << "handling " << tasks[i] << "\n";
    if (tasks[i] == 10)
        tasks.push_back(99);
}

Indexes survive reallocation because tasks[i] asks the vector afresh each time; use i < tasks.size() instead if follow-ups should be handled too. reserve() keeps references valid but does not legalize the range-for — end() is invalidated anyway. cppreference's iterator-invalidation table covers every container.

Takeaway: a range-for grabs begin/end once — grow the container and they dangle; a loop that feeds its own container must use indexes.

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

Open in Compiler Explorer ↗ Quiz this entry