how to erase from vector in range-based loop?
Asked Answered
K

5

13

I simply wanna erase the specified element in the range-based loop:

vector<int> vec = { 3, 4, 5, 6, 7, 8 };
for (auto & i:vec)
{
    if (i>5)
    vec.erase(&i);
}

what's wrong?

Kristinkristina answered 9/7, 2015 at 23:4 Comment(3)
Well it doesn't compile, let's begin there!Prink
@alejandro okay! you tell me a way to make it compile :)Kristinkristina
possible duplicate of Removing item from vector, while in c++11 range for loop?Termor
E
28

You can't erase elements by value on a std::vector, and since range-based loop expose directly values your code doesn't make sense (vec.erase(&i)).

The main problem is that a std::vector invalidates its iterators when you erase an element.

So since the range-based loop is basically implemented as

auto begin = vec.begin();
auto end = vec.end()
for (auto it = begin; it != end; ++it) {
  ..
}

Then erasing a value would invalidate it and break the successive iterations.

If you really want to remove an element while iterating you must take care of updating the iterator correctly:

for (auto it = vec.begin(); it != vec.end(); /* NOTHING */)
{
  if ((*it) > 5)
    it = vec.erase(it);
  else
    ++it;
}  
Eristic answered 9/7, 2015 at 23:9 Comment(0)
C
17

Removing elements from a vector that you're iterating over is generally a bad idea. In your case you're most likely skipping the 7. A much better way would be using std::remove_if for it:

vec.erase(std::remove_if(vec.begin(), vec.end(),
                          [](const int& i){ return i > 5; }),
           vec.end());

std::remove shift the elements that should be removed to the end of the container and returns an iterator to the first of those elements. You only got to erase those elements up to the end then.

Conclave answered 9/7, 2015 at 23:8 Comment(1)
We now have std::experimental::erase_if.Hartz
V
6

It's quite simple: don't use a range-based loop. These loops are intended as a concise form for sequentially iterating over all the values in a container. If you want something more complicated (such as erasing or generally access to iterators), do it the explicit way:

for (auto it = begin(vec); it != end(vec);) {
  if (*it > 5)
    it = vec.erase(it);
  else
    ++it;
}
Vortical answered 9/7, 2015 at 23:10 Comment(4)
vec out of range!Piave
@ifooi Er, what? Can you elaborate?Vortical
for (const auto &i : vec) std::cout<<vec[i]<<" "<< std::endl;Piave
Sorry; I was not correct and mess my code with thisPiave
B
1

Actually it IS technically possible, and works in both MSVC & GCC. But it is inadvisable as it produces warning "C++20-style initializer statement in a range-based 'for' statement is nonstandard in this mode (3230)" This is probably because its ambigous whether there is an internal duplicate iterator generated by the ranged expression auto& i: ints, so which iterator is checked in the implied !=ints.end()?

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

struct Int {
   Int(int i) : i(i) {} 
   ~Int(){ i = -i; }
   int i;
};

int  main() {
    vector<Int> ints;//{0,1,2,3,4};
    for (int i = 0; i<5; ++i)
      ints.emplace_back(Int(i));

    for (auto it = ints.begin();  auto& i: ints) { // requires C++20 ("ranged-based for with initializer")
      if (it->i == 3 || it->i == 2)
          ints.erase(it--); // Decrement after erasing a single element, and it preserves the iterator
       ++it;
    }
    for_each(
              ints.cbegin(),
              ints.cend(),
              [] (Int i) {cout << i.i << " ";} 
              );
   // outputs 0 1 4
}
Backset answered 24/10, 2022 at 4:19 Comment(0)
H
0

If you know you are going to terminate the loop (such as with break or return) after erasing the element, and you are not going to use any previously declared iterators or references to the vector, you can do it by computing the index of your current element, by subtracting &vec[0] from &i and then using std::next to convert this index into an iterator which can be erased ("How to convert a std::vector element's address into an iterator?"):

#include <iostream>
#include <vector>

int main() {
  std::vector<int> vec = { 3, 4, 5, 6, 7, 8 };
  for( auto& i : vec ) {
    if( i > 5 ) {
      vec.erase( std::next( vec.begin(), &i - &vec[0] ) );
      break;
    }
  }
  std::cout << "vector after range-based for :";
  for( auto& i : vec )
    std::cout << " " << i;
  std::cout << std::endl;
}

Prints this after element 6, the first element greater than 5, is erased:

vector after range-based for : 3 4 5 7 8

i will not be a valid reference after vec.erase() is called, and the loop cannot continue, since all iterators and references at and after i have been invalidated.

But I use this often in code, such as iterating through a vector to find out if a condition is true for one of its elements, and if it is, erase that element and stop further searching.

Haematoblast answered 24/8 at 2:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.