Consider this vector:
|0|1|2|3|4|5|6|7|8|9|
We use remove_if
to remove all elements that are multiples of 4:
std::remove_if(v.begin(), v.end(), [](auto i){ return i != 0 && !(i%4); });
This starts iterating through the vector with the X iterator until it finds an element where the predicate returns true:
|0|1|2|3|4|5|6|7|8|9| X
This is the first item we want to remove.
Then it creates another iterator pointing to the next element, Y = X + 1, and checks the predicate for * Y:
|0|1|2|3|4|5|6|7|8|9| XY
The predicate is false, so we want to save this element, so it assigns the next element to the element that we want to delete by doing *X = std::move(*Y)
:
|0|1|2|3|5|5|6|7|8|9| XY *X = std::move(*Y)
So, we have two iterators X and Y, where X points to the next element in "output" (that is, elements that we donβt delete), and Y is the next element to be deleted.
We move both iterators to the next position, check the predicate for Y (which is false again), and perform another assignment:
|0|1|2|3|5|6|6|7|8|9| XY *X = std::move(*Y)
Then he does the same in the following position:
|0|1|2|3|5|6|7|7|8|9| XY *X = std::move(*Y)
And then it moves on, but discovers that the predicate is true for Y
|0|1|2|3|5|6|7|7|8|9| XY
Thus, it simply increments Y, which skips this element and therefore does not copy it to the "output" position in X:
|0|1|2|3|5|6|7|7|8|9| XY
The predicate is not valid for Y, so it assigns it to X:
|0|1|2|3|5|6|7|9|8|9| XY *X = std::move(*Y)
Then it increases X and Y again
|0|1|2|3|5|6|7|9|8|9| XY
Now Y goes past the end, so we return X (which indicates the end of the output sequence, that is, the elements that we want to save).
After remove_if
X remove_if
we call v.erase(X, v.end())
, so the vector calls the destructors for each element from X to the end:
|0|1|2|3|5|6|7|9|~|~| X end
And then sets the size so that the vector ends with X:
|0|1|2|3|5|6|7|9| end
After that, v.capacity() >= v.size()+2
, because the memory that was used by the two finite elements is still present, but not used.