0

As we know, the erase-remove idiom can be used as a concise pattern to delete elements of a vector.

In particular, std::remove_if swaps elements inside the vector in order to put all elements that do not match the predicate towards the beginning of the container. These elements are then removed from the container.

This also means that if the predicate (body of the lambda function) returns true, then that element will be placed at the end of the vector.

It must be noted that within the erase remove idiom, both the erase and remove functions must get iterators to the same container. This means that if I try to provide a different iterator in the remove_if statement, the erase function must return an error.

But consider the code below:

vector<int> valsToRemove{ 4, 5, 6, 2, 3, 7, 10 };
    
vector<int> isValidToRemove{ 0, 1, 0, 1, 1, 0, 1 };
    
valsToRemove.erase(std::remove_if(isValidToRemove.begin(),
                                isValidToRemove.end(),
                                [&](int& x)-> bool { return x == 0; }), valsToRemove.end() );
                                
for( auto& val: valsToRemove ) {
        cout << val << "\t";
}

It turns out that I have a vector isValidToRemove which tells me whether an element of the valsToRemove vector must be retained or deleted based on "0" or "1" value. I use this within the remove_if statement to satisfy a predicate. In this case, due to different containers being used in the erase and remove_if functions, the erase function must return an error.

However, I get an arbitrary output which somehow appends additional elements of the vector. The output also varies across different instances/machines.

4 5 6 2 3 7 10 0 0 0 49 0 1 1 1 1

I just wanted to confirm if this should be the correct behavior or if it should be modified so that the correct error is returned to the user/client of this function.

Further, is there a concise way or a specific pattern to use a separate vector (such as isValidToRemove in the example above which might be received from an upstream component) to remove elements of a different vector (valsToRemove in the example above) instead of just using a for loop to remove these elements.

273K
  • 29,503
  • 10
  • 41
  • 64
  • 7
    *In this case, due to different containers being used in the erase and remove_if functions, the erase function must return an error.* No, what you do get is Undefined Behavior. C++ doesn't hold your hand in a lot of places and if you violate the language contract then anything is allowed to happen. – NathanOliver Mar 20 '23 at 02:08
  • Thanks. Is there a way to use a similar idiom like erase-remove in this case when I have a different vector specifying a criteria instead of a for loop? I mean what could be an efficient approach for this case. – Gaurav Dhir Mar 20 '23 at 02:10
  • I suggest you redesign your class so that the vector of ints are not decoupled from each other. `struct mydata { int data; bool remove;};`. Create a `std::vector`, and then set `remove` to either `true` or `false`. Then it becomes very easy to use the algorithm functions. – PaulMcKenzie Mar 20 '23 at 02:53
  • Thanks! I think based on your suggestion, a vector of a pair of integers might also work well. – Gaurav Dhir Mar 20 '23 at 04:18
  • Minor point: "`std::remove_if` swaps elements" -- it doesn't swap elements; it copies elements with the assignment operator. – Pete Becker Mar 20 '23 at 12:45
  • Thanks. I basically took this point from another stack overflow post. Also, based on [link](https://en.cppreference.com/w/cpp/algorithm/remove), I think that since C++-11, move assignment is being done by the remove_if method for values to be retained. The values to be retained are placed to the front and an iterator is returned to the new logical end of the range. The std::erase method then basically erases the values between logical and physical end of range. – Gaurav Dhir Mar 21 '23 at 03:31
  • 1
    "*is there a concise way or a specific pattern to use a separate vector ... to remove elements of a different vector*" - are you looking for something like this? `valsToRemove.erase(std::remove_if(valsToRemove.begin(), valsToRemove.end(), [&](int x) { return std::find(isValidToRemove,begin(), isValidToRemove,end(), x) != isValidToRemove.end(); }), valsToRemove.end() );` which can be simplified to this: `std::erase_if(valsToRemove, [&](int x){ return std::ranges::contains(isValidToRemove, x); });` – Remy Lebeau Mar 22 '23 at 16:35

0 Answers0