-1

Today I was doing a bit of code, which looked something like this:

vec.erase(std::remove_if(vec.begin(), vec.end(), <lambda here>));

When above code was not supposed to erase anything, meaning that std::remove_if should return vec.end(), I was very surprised when i received my vector with size decreased by one: last element was erased. Problem was fixed by changing above to:

vec.erase(std::remove_if(vec.begin(), vec.end(), <lambda here>), vec.end());

But still question remains: how can

vec.erase(vec.end());

do any work? Shouldn't it be undefined behaviour?

Edit: Apparently my understanding of underfined behaviour was wrong from the beginning and what i observed was UB all along. Thank you everyone for answering my question.

Gettor
  • 51
  • 6
  • 1
    You're now entering the *Land of UB* – erip Dec 22 '15 at 12:01
  • 3
    Please fix the question, `std::remove_if` takes **three** arguments, not just a lambda. I assume you mean `std::remove_if(vec.begin(), vec.end(), )`? Since the entire point of your question revolves around different numbers of arguments to functions it is confusing to have to mentally insert arguments where the omission is not relevant to the question. Please take a minute longer to create a better question, to save hundreds of minutes of readers trying to understand it. – Jonathan Wakely Dec 22 '15 at 12:04
  • 2
    What do you expect to happen when you encounter undefined behaviour? – Martin Bonner supports Monica Dec 22 '15 at 12:05
  • JonathanWakely, thanks for pointing that out. Of course my first version was just shortcut for what you described MartinBonner - I would expect erase method *not to* erase anything. This (first) code of mine is of course bugged and this is why i fixed it, but i don't think it should behave as if erasing of last element took place. – Gettor Dec 22 '15 at 12:37
  • 1
    It's **undefined**. You can't say "when I write crap I expect it to do nothing". That's not how C++ works. That would not be "undefined" that would be "defined to do nothing if you use it wrong". – Jonathan Wakely Dec 22 '15 at 12:48
  • Was there an element to remove that passed the remove_if test? – Yakk - Adam Nevraumont Dec 22 '15 at 12:59

3 Answers3

3

It is undefined behaviour. In particular, it means that you may see size decreased by one

More on UB

Community
  • 1
  • 1
RiaD
  • 46,822
  • 11
  • 79
  • 123
  • 3
    ... or silently disable the virus checker and firewall on your computer, or *anything*. – Martin Bonner supports Monica Dec 22 '15 at 12:06
  • You missed something obvious. Setting aside the botched reference to std::remove_if, the two-parameter version of std::vector::erase() is certainly NOT undefined behavior, when combined with std::remove_if, in this context. The questioner is confusing the two parameter erase() method with the one parameter version. – Sam Varshavchik Dec 22 '15 at 12:10
  • @SamVarshavchik, that's not what the question is though. Read the title and the repeated question after "But still question remains:" – Jonathan Wakely Dec 22 '15 at 12:11
  • Yes, but this is the case where it's clear that he is actually referring to the two parameter version of erase() that's used in the original code, that's obviously misquoted here. – Sam Varshavchik Dec 22 '15 at 12:12
  • 1
    @SamVarshavchik, no, it says "I was surprised the one argument form removed something. I fixed it by using the two argument form, but the question remains, why did the one-argument form remove something?" You missed something obvious: the question is about the one-argument overload. – Jonathan Wakely Dec 22 '15 at 12:13
1

Why erasing vector.end() is allowed?

It isn't allowed. The argument to vector::erase(const_iterator) must be a valid dereferenceable iterator into the vector. The past-the-end iterator is valid but not dereferenceable.

how can [...] do any work? Shouldn't it be undefined behaviour?

Why do you think it can't do work and be undefined behaviour?

What do you think undefined behaviour means?

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I just didn't think undefined behaviour would disguise itself as "defined" behaviour. After all that piece of code did something, which vaguely makes sense in terms of vector::erase. – Gettor Dec 22 '15 at 12:42
  • Except it didn't really, it only decreased the vector's size by one, it didn't actually destroy any elements. But anyway, your understanding of undefined behaviour is wrong. "Appearing to work correctly" is one consequence of undefined behaviour. – Jonathan Wakely Dec 22 '15 at 12:46
1

First of all, it's not

vec.erase(std::remove_if(<lambda here>), vec.end());

It is really

vec.erase(std::remove_if(vec.begin(), vec.end(), <lambda here>), vec.end());

Which is something completely different from

vec.erase(vec.end());

The latter, vec.erase(vec.end()), is certainly undefined behavior. The former is not.

std::remove_if(vec.begin(), vec.end(), <lambda here>)

This removes the values in the vector that are matched by the lambda, and -- most importantly -- this returns the ending value of the new sequence.

vec.erase(first, last);

This removes the values in the vector starting from the first iterator value, and up to but not including the last iterator value.

Put the two together, and you are removing matching values from the vector, and then shrinking the vector accordingly.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Now hold on. http://www.cplusplus.com/reference/vector/vector/erase/ Vector::erase is supposed to work in two ways. First is as you described with two iterators pointing to range limits, but second one uses only one argument and should delete element at that given position (iterator). std::remove_if returns iterator to *past the last* element not to be removed. In that case: to vec.end(). So vector::erase receives iterator to vec.end() and still erases last element ( vec.end() - 1 ). This is what confuses me. – Gettor Dec 22 '15 at 12:44
  • std::remove_if() returns the iterator to past the last element **of the new sequence**. If your original vector had five elements, std::remove_if() removed two of them, then there will be three elements left, and std::remove_if() returns an iterator pointing to the fourth element of the vector. The vector still has five elements left. The returned iterator is not end(), but begin()+3. std::remove_if() works on any iterator sequence, and it simply "repacks" the sequence. – Sam Varshavchik Dec 22 '15 at 13:35
  • I'm going to guess that the original code's lambda always matches one element in the vector, which is why the single parameter version of erase() always works, and always does the right thing. – Sam Varshavchik Dec 22 '15 at 13:36