Consider the following program:
#include <list>
#include <cstdio>
int main() {
std::list<int> l;
std::list<int>::iterator it = l.begin();
l.push_back(0);
l.insert(it, 1);
for(const int &i: l) {
printf("%d", i);
}
}
(http://cpp.sh/66giy)
This prints 01. Very surprising. If I change the list to a deque, it prints the expected 10.
Is this a bug?
EDIT: The deque behavior is irrelevant, iterators to deques are invalidated by push_back.
I can't catch your problem... ok, lets try to reproduce:
std::list<int> l;
std::list<int>::iterator it = l.begin();
What is your iterator pointing to? To the end of the list as the list is empty!
ยง23.2.1 [container.requirements.general] p6
begin() returns an iterator referring to the first element in the container. end() returns an iterator which is the past-the-end value for the container. If the container is empty, then begin() == end();
l.push_back(0);
Now the list contains a single element. Your iterator is valid as a list did not invalidate the iterator and points still to the end of the list.
l.insert(it, 1);
Now you insert 1 before the iterator which points still to the end. So your first element is a 0 and the last one is a 1.
So your output is 01 as expected.
Maybe your expectation that the begin() delivers a fixed virtual start of container iterator is simply wrong?
Related
So I need to erase elements from a std::set in a particular order, doing something with the first.
so if I had a set containing {1,2,3,4,5,6} and my I wanted to go until 4, I need to:
doSomething(6);
erase(6);
doSomething(5);
erase(5);
doSomething(4);
erase(4);
I have the following code that does not work:
#include <iostream>
#include <set>
void doSomething(int value) {
std::cout << value << '\n';
}
int main() {
std::set<int> s = {1,2,3,4,5,6};
auto beginIt = s.end();
auto endIt = s.lower_bound(4);
auto rbegin = std::make_reverse_iterator(beginIt);
auto rend = std::make_reverse_iterator(endIt);
for (auto it = rbegin; it != rend;) {
doSomething(*it);
s.erase(std::next(it).base());
}
return 0;
}
I think the issue is that it erasing the end iterator then keeps going util it crashes.
How can I get this to work.
godbolt: https://godbolt.org/z/KvaGWhr4G
The correct way of doing what you want is to getting your end iterator each time.
for (auto it = rbegin; it != std::make_reverse_iterator(s.lower_bound(4));) {
doSomething(*it);
s.erase(std::next(it).base());
}
Now let's see why you initial code didn't work.
In set, the iterators are not invalidated after erasing an element, EXCEPT for the iterator that was pointing to the erased element.
Now let's see what happened in the last iteration when you remove 4.
When dereferencing the rend, we will see that it points to 3. However, the base of rend points to 4. And after removal of 4, the base of rend has been invalidated. So your program had Undefined behavior.
To understand why getting end iterator at every iteration works, we have to understand that during the program the base of it is always s.end(). And at the last step, when we call s.lower_bound(4), we get s.end(). Hence, the condition for exiting the loop is satisfied.
I have a std::vector and an iterator that points to an element in the vector. My question is how can I delete an element from the vector and keep the iterator?
I've tried using a second iterator to find the specific element that I want to delete and after erasing it with the erase function, the first iterator becomes invalid.
I have a std::vector and an iterator that points to an element in the vector. My question is how can I delete an element from the vector and keep the iterator?
Please note that when an element is deleted, no iterator can point to it as it ceases to exist. So, to reference it's location normal practice is just use the returned iterator from the erase() method. This allows use of the insert() method which will put a value in the position of the previously erased object.
With one iterator, just use this:
auto loc_after = v.erase(iter); // since the element has been erased loc_after points to the position the erased element had
I've tried using a second iterator to find the specific element that I want to delete and after erasing it with the erase function, the first iterator becomes invalid.
In the case of two iterators the elements can be easily erased by erasing the physically last iterator first since the earlier iterator is not invalidated. This is encapsulated in a function. The returned iterator points to one past the position of the "first" iterator regardless of order between the first and second iterator.
#include <vector>
#include <iostream>
// This returns an iterator positioned after where first_iter was before being erased
// this allows the insert(pos, val) method to insert a value in the the location just prior to pos
std::vector<int>::iterator second_iterator_loc(std::vector<int>& v, std::vector<int>::iterator first_iter, std::vector<int>::iterator second_iter)
{
std::vector<int>::iterator iter;
if (first_iter < second_iter)
{
v.erase(second_iter);
iter = v.erase(first_iter);
}
else if (second_iter < first_iter)
{
auto dist = first_iter - second_iter;
v.erase(first_iter);
iter = v.erase(second_iter) + dist - 1;
}
else
{
;// handler in case both iterators point to the same object
}
return iter;
}
int main()
{
std::vector<int> v{ 1,2,3,4,5 };
std::vector<int> v2 = v;
std::vector<int>::iterator iter1 = v.begin() + 1; // points to 2 in v
std::vector<int>::iterator iter2 = v.begin() + 3; // points to 4 in v
std::vector<int>::iterator iter;
iter = second_iterator_loc(v, iter1, iter2);
v.insert(iter, 9); // inserts a 9 in the previous location of the "first" iterator (where "2" was)
for (auto x : v)
std::cout << x << '\n'; // prints: 1 9 4 5
v = v2;
iter1 = v.begin() + 3; // reverse iterator positions
iter2 = v.begin() + 1;
iter = second_iterator_loc(v, iter1, iter2);
v.insert(iter, 9); // inserts a 9 in the previous location of the "first" iterator (where "4" was)
for (auto x : v)
std::cout << x << '\n'; // prints: 1 3 9 5
}
My question is how can I delete an element from the vector and keep the iterator?
You can't using std::vector::iterator. The iterator will be invalidated by erasing the element.
But you could achieve this by writing your own iterator class that stores a pointer to the vector and an index.
std::vector::erase will invalidate all iterators at or after the erased element:
Invalidates iterators and references at or after the point of the
erase, including the end() iterator.
However, erase will return an iterator pointing to the element following the last removed element. Maybe that is enough to satisfy your use case?
Why does the following code crash? And what should I do when I am iterating via reverse iterator. How do I erase individual elements then?
deque q;
q.push_back(4);
q.push_back(41);
q.push_back(14);
for (auto it = q.begin(); it != q.end();) {
auto current = it;
q.erase(current);
it++;
}
Why does the following code crash ? How do I erase individual elements then ?
std::deque::erase invalidates iterators.
All iterators and references are invalidated, unless the erased elements are at the end or the beginning of the container, in which case only the iterators and references to the erased elements are invalidated.
The past-the-end iterator is also invalidated unless the erased elements are at the beginning of the container and the last element is not erased.
In your code, the iterators to the element to be erased (i.e. it and current) will become invalid after q.erase(current), then it++ will lead to UB.
You could make use of the return value of std::deque::erase
Iterator following the last removed element. If the iterator pos refers to the last element, the end() iterator is returned.
for (auto it = q.begin(); it!=q.end(); )
{
it = q.erase(it);
}
And what should I do if I am iterating via reverse iterator.
Because std::deque::erase doesn't accept reverse_iterator as parameters, you need to use base() to convert it to normal iterator (pay attention to the position conversion). Such as
for (auto it = q.rbegin(); it!=q.rend(); )
{
it = std::make_reverse_iterator(q.erase((++it).base()));
}
As per C++11 23.3.3.4 deque modifiers /4, deque iterators become invalid if you delete certain elements.
An erase operation that erases the last element of a deque invalidates only the past-the-end iterator and all iterators and references to the erased elements.
An erase operation that erases the first element of a deque but not the last element invalidates only the erased elements.
An erase operation that erases neither the first element nor the last element of a deque invalidates the past-the-end iterator and all iterators and references to all the elements of the deque.
In your case, you're usually only ever erasing the first element so it will only invalidate that element. That means the it++ is invalid and you should instead use something like:
it = q.erase(it);
inside the loop, since the erase call itself returns an "adjusted" iterator. This will also work when removing the last element.
However, since your code is totally clearing the list (assuming it's not a cut down version of something which needs to process each element), you can ditch the loop altogether and just use:
q.clear();
As the other answerers have already pointed out, erasing elements from the queue will invalidate the iterators you are using to iterate its elements. Thus it fails.
But I assume that you don't intend to erase all elements in the queue, in which case you probably would have rather used:
q.erase(q.begin(), q.end());
or
q.clear();
Therefore, I'd like to suggest using another technique, that can be used to delete items matching certain criteria from a queue: the erase-remove idiom.
Here, the functions std::remove(...) and std::remove_if(...) are used to move the items to be deleted (matching certain criteria) to the end of the container. The range-based version of q.erase(...) is then used to delete the items.
Here's an example:
#include <deque>
#include <algorithm>
#include <iostream>
// predicate function for removal of elements
bool greater_three(int x) {
return x > 3;
}
int main() {
std::deque<int> q = {1,2,3,4,5};
for (auto i : q) std::cout << i << " "; std::cout << "\n";
// delete all items with value 3
q.erase(std::remove(q.begin(), q.end(), 3), q.end());
for (auto i : q) std::cout << i << " "; std::cout << "\n";
// delete all items with value > 3
q.erase(std::remove_if(q.begin(), q.end(), greater_three), q.end());
for (auto i : q) std::cout << i << " "; std::cout << "\n";
}
The output is:
$ g++ test.cc -std=c++11 && ./a.out
1 2 3 4 5
1 2 4 5
1 2
For reference:
http://en.cppreference.com/w/cpp/container/deque/erase
http://en.cppreference.com/w/cpp/container/deque/clear
http://en.cppreference.com/w/cpp/algorithm/remove
q clearly doesn't support removing elements while iterating through them.
I have myVector with some values of the type Texture_Map which is just an int.
I'm not erasing anything... I just want to insert a value in each even iteration.
Something like:
If I have this vector [1,2,3,4,5]
And my Texture_Map::TEXTURE_FLIP is 99, then my final vector should be:
[99,1,99,2,99,3,99,4,99,5]
After the first insert() I get the "Vector iterator not incrementable problem" error.
The code:
myVector.push_back(Texture_Map::TEXTURE_INIT);
for(unsigned int i = 0 ; i < m_max_pieces-2; i++)
myVector.push_back((Texture_Map::TextureID)i);
std::random_shuffle(myVector.begin(), myVector.end());
std::vector<Texture_Map::TextureID>::iterator it = myVector.begin();
for (it=myVector.begin(); it<myVector.end(); it++)
{
myVector.insert(it,Texture_Map::TEXTURE_FLIP);
}
Thanks!
As part of the consequence of using insert on a vector is that it:
Causes reallocation if the new size() is greater than the old capacity(). If the new size() is greater than capacity(), all iterators and references are invalidated. Otherwise, only the iterators and references before the insertion point remain valid. The past-the-end iterator is also invalidated.
The issue you're seeing is that you're attempting to increment an iterator which is no longer valid - it's past the insertion point, so it's invalidated. The solution here is to take advantage of that fact that insert returns an iterator:
iterator insert( iterator pos, const T& value );
Specifically:
Return value
1-2) Iterator pointing to the inserted value
So you want:
for (it=myVector.begin(); it<myVector.end(); it++) {
// now it will point to TEXTURE_FLIP
it = myVector.insert(it,Texture_Map::TEXTURE_FLIP);
// now it will point back to the original element
++it;
}
There have been a few questions regarding this issue before; my understanding is that calling std::vector::erase will only invalidate iterators which are at a position after the erased element. However, after erasing an element, is the iterator at that position still valid (provided, of course, that it doesn't point to end() after the erase)?
My understanding of how a vector would be implemented seems to suggest that the iterator is definitely usable, but I'm not entirely sure if it could lead to undefined behavior.
As an example of what I'm talking about, the following code removes all odd integers from a vector. Does this code cause undefined behavior?
typedef std::vector<int> vectype;
vectype vec;
for (int i = 0; i < 100; ++i) vec.push_back(i);
vectype::iterator it = vec.begin();
while (it != vec.end()) {
if (*it % 2 == 1) vec.erase(it);
else ++it;
}
The code runs fine on my machine, but that doesn't convince me that it's valid.
after erasing an element, is the iterator at that position still valid
No; all of the iterators at or after the iterator(s) passed to erase are invalidated.
However, erase returns a new iterator that points to the element immediately after the element(s) that were erased (or to the end if there is no such element). You can use this iterator to resume iteration.
Note that this particular method of removing odd elements is quite inefficient: each time you remove an element, all of the elements after it have to be moved one position to the left in the vector (this is O(n2)). You can accomplish this task much more efficiently using the erase-remove idiom (O(n)). You can create an is_odd predicate:
bool is_odd(int x) { return (x % 2) == 1; }
Then this can be passed to remove_if:
vec.erase(std::remove_if(vec.begin(), vec.end(), is_odd), vec.end());
Or:
class CIsOdd
{
public:
bool operator()(const int& x) { return (x % 2) == 1; }
};
vec.erase(std::remove_if(vec.begin(), vec.end(), CIsOdd()), vec.end());