c++stdvectoreraseremove-if

using erase and remove-if: passing more than one argument to function


I would like to iterate and erase items from std::vector, but I wish to compare each item to previous and next items in the vector and to perform some calculations. If a certain condition is met, item will be erased. Is it possible to pass more than one argument, iterators for previous, current and next vector items, to the function remove-if receives? if not, how can I access previous and next iterator items inside the function passed to remove-if?


Solution

    1. As pointed out in the comments, you need an intermediate container to store if the elements have to be removed, because if the previous has been removed, it cannot be used to check if the next has to be removed.
    2. Previous and next may fall out of the container. The predicate function (aka test or check) can take pointers instead of references, so that nullptr can signal an out-of-container element.

    Implementation:

    template<class _FwdIt, class _Pr>
    inline _FwdIt remove_if_with_prev_and_next(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
    {
        if (_First == _Last) {
            return _First;
        }
        auto first = _First;
        auto next = std::next(first);
        std::vector<char> remove; // vector<bool> is bad:-(
        remove.push_back(_Pred(nullptr, *first, next == _Last ? nullptr : &*next) ? 1 : 0);
        while (next != _Last) {
            auto prev = first;
            first = next;
            ++next;
            remove.push_back(_Pred(&*prev, *first, next == _Last ? nullptr : &*next) ? 1 : 0);
        }
        auto remove_it = remove.begin();
        // assume remove_if calls the predicate only once for every element, in order
        return std::remove_if(_First, _Last, [&remove_it](const auto&) { return *remove_it++ > 0; });
    }
    
    int main()
    {
        std::vector<int> v{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
        std::list<int> l{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
        auto equal_to_prev_or_next = [](const int* prev, int el, const int* next) { return (prev && *prev == el) || (next && *next == el); };
        v.erase(remove_if_with_prev_and_next(v.begin(), v.end(), equal_to_prev_or_next), v.end());
        l.erase(remove_if_with_prev_and_next(l.begin(), l.end(), equal_to_prev_or_next), l.end());
    }
    

    Instead of storing all checks in a vector, testing one element could first precompute if the next should be removed. Implementation:

    template<class _FwdIt, class _Pr>
    inline _FwdIt remove_if_with_prev_and_next_2(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
    {
        if (_First == _Last) {
            return _First;
        }
        auto first = _First;
        auto next = std::next(first);
        bool remove = _Pred(nullptr, *first, next == _Last ? nullptr : &*next);
        auto get_remove_this_and_test_next = [&remove, &first, &next, &_Last, &_Pred](const auto&) {
            bool remove_this = remove;
            if (next != _Last) {
              auto prev = first;
              first = next;
              ++next;
              remove = _Pred(&*prev, *first, next == _Last ? nullptr : &*next);
            }
            return remove_this;
        };
        // assume remove_if calls the predicate only once for every element, in order :-(
        return std::remove_if(_First, _Last, get_remove_this_and_test_next);
    }
    
    int main()
    {
        std::vector<int> v{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
        std::list<int> l{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
        auto equal_to_prev_or_next = [](const int* prev, int el, const int* next) { return (prev && *prev == el) || (next && *next == el); };
        v.erase(remove_if_with_prev_and_next_2(v.begin(), v.end(), equal_to_prev_or_next), v.end());
        l.erase(remove_if_with_prev_and_next_2(l.begin(), l.end(), equal_to_prev_or_next), l.end());
    }
    

    Both implementations assume that remove_if calls the predicate only once per element and in order. This is sane to assume, but not guaranteed. If you want to be 100% sure that it works, replace the call to remove_if by this possible implementation (Second version).