1

If I erase all 5 elements of a vector during first iteration of a standard for loop

std::vector<int> test {1, 2, 3, 4, 5};

for(int i = 0; i < test.size(); i++)
{
    if(test[i] == 1) test.erase(test.begin(), test.end());
    std::cout << i << " ";
}

it will iterate only once and the std::cout output will be '0'.

However if I do the same thing using a range-based loop, it will iterate 5 times, despite all elements of vector being erased.

int i = 0;
for (auto &a: test)
{
    if (a==1) test.erase(test.begin(), test.end());
    std::cout << i << " ";
    i++;
}

And the std::cout output will be '0 1 2 3 4'.

Where does such different behaviour come from, when using those two types of loops?

NutCracker
  • 11,485
  • 4
  • 44
  • 68
Dawid
  • 47
  • 5

2 Answers2

3

In the first case, for each iteration std::vector::size function is being called. Thus, if you delete all the elements in the first iteration, std::vector::size function which is called before the start of second iteration will return 0. Therefore, second iteration won't happen because the condition i < test.size() is not satisfied.

In the second case, range-based for loop uses iterators instead of std::vector::size function. When you call std::vector::erase you invalidate all the iterators including the end() iterator. Therefore, second case is actually UB (Undefined Behavior) and you should never rely on that.

From the docs:

std::vector::erase

... Invalidates iterators and references at or after the point of the erase, including the end() iterator.

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • Thank you for the answer. In the first case it seemed logical to me that the iteration ends once size of vector is equal to 0 but I struggled to understand why in the second case it doesn't end. The fact that it's undefined behavior explains a lot. – Dawid Apr 05 '20 at 10:06
2

From the standard, [stmt.ranged]/1

The range-based for statement

for ( init-statement opt for-range-declaration : for-range-initializer ) statement

is equivalent to

{
  init-statement opt     
  auto &&range = for-range-initializer ;
  auto begin = begin-expr ;
  auto end = end-expr ;
  for ( ; begin != end; ++begin ) {
      for-range-declaration = * begin ;
      statement
  }
}

Note that the begin and end iterators are initialized before the loop itself begins. If you perform erasing in the loop which making the iterators invalid then the code leads to UB.

On the other hand, the 1st code snippet checks test.size() every iteration. When the elements get erased and the size() beomes 0 the loop ends immediately.

Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • So that's undefined behaviour. Thanks a lot. – Dawid Apr 05 '20 at 10:06
  • Why are you using underscores in names? https://stackoverflow.com/questions/3136594/naming-convention-underscore-in-c-and-c-sharp-variables/20496955 – northerner Apr 05 '20 at 11:20
  • @northerner It's not me, I got it from [cppreference](https://en.cppreference.com/w/cpp/language/range-for#Explanation), and I think it's written at the aspect of the language (or compiler). Anyway, I changed the pseudocode to the one got from the standard. – songyuanyao Apr 05 '20 at 13:20