Pass additional arguments to remove_if

dynamic picture dynamic · Jun 4, 2013 · Viewed 9.7k times · Source

I would like to remove elements from a vector using remove_if function but limiting the erasing to N elements.

Example:

// predicate function that determines if a value is an odd number.
bool IsOdd (int i) {

  if (we deleted more than deleteLimit)
    return false;

  return ((i%2)==1);
}


void otherFunc(){
  int deleteLimit = 10;

  // remove odd numbers:                       
  std::vector<int>::iterator newEnd =
  std::remove_if (myints.begin(), myints.end(), IsOdd (how to pass deleteLimit?) );
}

I need that IsOdd predicate stores how many elements it has removed and how many we want to delete. The only way is to use a global variable? Like this:

int deleteLimit = 10;
int removedSoFar = 0;
bool IsOdd (int i) {

  if (deleteLimit < removedSoFar)
    return false;

  if (i%2==1) {
    removedSoFar++
    return true;
  }

  return false;

}
remove_if ...

Answer

leemes picture leemes · Jun 4, 2013

The state telling "how many elements have been removed so far" should be defined outside of the function / the call to the algorithm. This is because a functor should not have a state which is modified when being called (this would be undefined behavior).

You should take a reference to this state (counter) in the constructor of the functor (or capture by reference in the lambda), so you can access and modify this counter. When this functor is now copied, it doesn't matter which one is called by the algorithm, since all of them now hold the reference to the same state.

Using functors (C++03):

class IsOdd {
    int deleteLimit;
    int & deletedSoFar;

public:
    IsOdd(int deleteLimit, int & deletedSoFar) :
        deleteLimit(deleteLimit), deletedSoFar(deletedSoFar)
    {}

    bool operator()(int i) const {
        if (deletedSoFar < deleteLimit && i % 2) {
            ++deletedSoFar;
            return true;
        }
        return false;
    }
};

int deletedSoFar = 0;
int deleteLimit = 10;
std::remove_if (myints.begin(), myints.end(), IsOdd(deleteLimit, deletedSoFar));

Using lambda (C++11):

int deletedSoFar = 0;
int deleteLimit = 10;
auto it = std::remove_if (myints.begin(), myints.end(), [deleteLimit,&deletedSoFar](int i){
    if (deletedSoFar < deleteLimit && i % 2) {
        ++deletedSoFar;
        return true;
    }
    return false;
});
myints.erase(it, myints.end());