21
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector<int> a = {1,2,3,7,1,5,4};
    vector<int> b = {6,7,4,3,3,1,7};
    a.erase(remove(a.begin(),a.end(),a[0]),a.end());
    b.erase(remove(b.begin(),b.end(),b[0]),b.end());

    return 1;
}

For this specific example, my GNU gdb Ubuntu 7.7.1 states that at return 1 line: a = {2,3,7,1,5,4} which is not expected (only deletes one 1), and b = {7,4,3,3,1} which is not expected.

My expectation is b should be a=2,3,7,5,4 and b=7,4,3,3,1,7.

What's happening here?

timrau
  • 22,578
  • 4
  • 51
  • 64
user3064869
  • 530
  • 1
  • 6
  • 19
  • 2
    Wouldn't you expect the first one to remove all the 1s and result in `{2,3,7,5,4};`? – juanchopanza Feb 25 '15 at 15:00
  • 4
    Why is the first result as expected? Shouldn't it be `2,3,7,5,4` (both `1`s get removed)? I believe you're violating some precondition by passing a reference to a member of the `vector` you're iterating over. Both lines can be fixed by making a copy - `+a[0]` and `+b[0]` – Praetorian Feb 25 '15 at 15:00
  • 1
    This is actually pretty close to an SSCCE. It just needs the output code. – Fred Larson Feb 25 '15 at 15:01
  • first comment so true, but indeed the "expected" behaviour makes no sense, so i guess it's a standard question ? – Guiroux Feb 25 '15 at 15:02
  • That's actually true I didn't notice the first one is wrong as well. – user3064869 Feb 25 '15 at 15:04
  • 1
    @Praetorian I can understand your desire for conciseness, but isn't using unary `+` for this bordering on obfuscation? (In theory, `int(a[0])` should also work; the result is an rvalue, and using it to initialize a reference shouldn't result in an alias to any existing value, anywhere. I don't know if I'd feel comfortable counting on a compiler not optimizing this, however. And it's not really more explicit either; the lvalue-rvalue distinction is often very subtle.) – James Kanze Feb 25 '15 at 15:08
  • @JamesKanze Of course it is, both unary `+` and `int(a[0])` fall into the same obfuscation bucket and I'd cry foul if I saw code like that in a review for instance. But when posting a comment on SO ... fewer keystrokes FTW :) If I were posting an answer instead I'd have stored the value in a temp variable as shown below. – Praetorian Feb 25 '15 at 15:14
  • 3
    Why do you `return 1` instead of `0`? – Lightness Races in Orbit Feb 25 '15 at 15:20

1 Answers1

20

The declaration of std::remove() looks like

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);

Note that the last parameter is a reference. Thus after compilation it effectively pass the address of the specified element.

By remove(a.begin(), a.end(), a[0]), something indicating the address to the 0th element of a is passed in. When remove() is running, once the 0th element is handled, the value pointed by the reference passed in changed, which leads to the unexpected result.

To get expected result, make a copy before calling std::remove().

int toberemoved = a[0];
a.erase(remove(a.begin(),a.end(),toberemoved),a.end());
timrau
  • 22,578
  • 4
  • 51
  • 64
  • 5
    That was my first reaction too. But the standard doesn't make this requirement. Which means that the implementation of `std::remove` must take the necessary precautions, and if he has the problem you describe, this is an error in the library. Or maybe in the standard, because maybe the intent was that this shouldn't be required to work. – James Kanze Feb 25 '15 at 15:05
  • 4
    Please don't spread the myth that the abstract machine defined by the C++ standard makes any connection between references and memory addresses. – Lightness Races in Orbit Feb 25 '15 at 15:21
  • 4
    @JamesKanze I'm not sure it has to explicitly make that requirement. `std::remove` changes the values stored at various iteration locations. Such changes change the value referred to in the last parameter. As `std::remove` is specified in terms of `==` on `value`, the fact that you told it to change the value of value means that if `==` returns something different, that is the callers problem not the algorithms. So long as it does valid operations (ie, it removes elements from the range in a stable manner), and it calls `==`, the `remove` implementation follows the standard. – Yakk - Adam Nevraumont Feb 25 '15 at 15:54
  • @Yakk I'm not sure myself; the standard really should make it clear, perhaps through some global statement concerning arguments passed by reference. The fact that the standard _does_, in other cases, specify the lack of aliasing as a requirement is suggestive that in the absence of such a specific requirement, the user is entitled to expect that it work. – James Kanze Feb 25 '15 at 16:36