std::erase and std::remove combination to delete specific element doesn't work for specific example
Asked Answered
E

1

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?

Estuarine answered 25/2, 2015 at 14:52 Comment(8)
Wouldn't you expect the first one to remove all the 1s and result in {2,3,7,5,4};?Falconiform
Why is the first result as expected? Shouldn't it be 2,3,7,5,4 (both 1s 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]Keiko
This is actually pretty close to an SSCCE. It just needs the output code.Injudicious
first comment so true, but indeed the "expected" behaviour makes no sense, so i guess it's a standard question ?Varityper
That's actually true I didn't notice the first one is wrong as well.Estuarine
@Keiko 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.)Eudy
@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.Keiko
Why do you return 1 instead of 0?Kirbie
I
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());
Invoke answered 25/2, 2015 at 15:2 Comment(4)
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.Eudy
Please don't spread the myth that the abstract machine defined by the C++ standard makes any connection between references and memory addresses.Kirbie
@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.Ratha
@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.Eudy

© 2022 - 2024 — McMap. All rights reserved.