Using negation of UnaryPredicate in erase-remove idiom
Asked Answered
S

2

6

Consider the following scenario:

bool is_odd(int i)
{
    return (i % 2) != 0;  
}
int main()
{
    // ignore the method of vector initialization below.
    // assume C++11 is not to be used.
    std::vector<int> v1 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    std::vector<int> v2 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    
    // removes all odd numbers, OK
    v1.erase( std::remove_if(v1.begin(), v1.end(), is_odd), v1.end() );

    // remove all even numbers
    v2.erase( std::remove_if(v2.begin(), v2.end(), ???), v2.end() );
}

Can I use the same is_odd() UnaryPredicate to remove even numbers as expected in the last line of main(). Or will I have to necessarily write a is_even() even though it will be nothing but:

bool is_even(int i)
{
    return !is_odd(i);  
}
Subbasement answered 17/12, 2015 at 8:37 Comment(9)
If you have C++11, a lambda is easiest: [](int i){ return !is_odd(i);}, and I would claim clearer than something like not1.Ring
sorry did notice the = before the curly bracesNigel
@HumamHelfawi: even with the = the above code requires C++11! There is no direct way to initialize a std::vector (or any of the other standard library containers) with a sequence of elements in C++ prior to C++11. The way to do it would be creation of an array and then using iterators to the array with the construct of the container.Pun
I was shocked too but I read some answer on stackoverflow that advice the asker to use it if no c++11 he called it array inilizing not initializer listNigel
I should review some reference about it thanks for notifying meNigel
Added C++03 tag. The current standard is C++14; when I see just C++, I assume a reasonably recent compiler. (Even though that is not the case at work, but such is life).Ring
@HappyCoder It's easy to forget that your circumstances might not be the same as someone else's. I'm in the same position, we have to support several compilers, which means being held back to what is supported in the worst (and slowest to catch up).Ring
I tried it on older compiler yes you are right @DietmarKühl it is not workingNigel
@HappyCoder eaither add C++11 or change the way of inilizing :P .. just kiddingNigel
N
10

check the std::not1 function. it does what you want.

v2.erase( std::remove_if(v2.begin(), v2.end(), std::not1(std::ptr_fun(is_odd))), v2.end() );

Live example

Anyway, if it is up to me plus C++11 is available I would prefere:

 v2.erase( std::remove_if(v2.begin(), v2.end(), [&](auto/* or the type */ const& item){return !is_odd(item);}), v2.end() );

because as far as I remember std::not1 was helpful before lambda was available.

Nigel answered 17/12, 2015 at 8:39 Comment(0)
C
5

You can use std::not1. Sadly, std::not1 requires a function object argument with nested argument_type and result_type types. That is, it can't be used directly. Instead, it is necessary to combine the use with std::ptr_fun when using the negator with a normal function:

    v2.erase( std::remove_if(v2.begin(), v2.end(), std::not1(std::ptr_fun(is_odd))), v2.end() );

At the last committee meeting std::not_fn was moved from the Library Fundamentals TS 2 into the working draft. That is, there is hope that with C++17 there is a better offer for a generic negator.

In general, the fun stops when you need to use any of the std::*_fun functions. As others have pointed out, it may be reasonable to use a lambda instead:

v2.erase( std::remove_if(v2.begin(), v2.end(), [](auto&& x){ return !::is_odd(x); }), v2.end() );

The use of a lambda function or a function object with an inline function call operator also has the advantage that the compiler has an easier time inlining the code.

Obviously, if you need to use C++ prior to C++11 the std::not1/std::ptr_fun approach is the easiest for immediate use an the use of a lambda function isn't even possible. In that case you may want to create a simple function object to support inlining:

struct not_odd {
    template <typename T>
    bool operator()(T const& value) const { return !::odd(value); }
};
// ...
v2.erase( std::remove_if(v2.begin(), v2.end(), not_odd()), v2.end() );
Capri answered 17/12, 2015 at 8:54 Comment(2)
"the fun stops when you need to use any of the std::*_fun functions". Is it just the fun, or is there any technical/practical downside as well?Subbasement
@HappyCoder: the comment is somewhat tongue-in-cheek but it is based on the experience that the need to use std::ptr_fun is easily forgotten (as the other answer demonstrated) and, more so, that using the correct version of the std::mem*_fun version with member function pointer is not at all obvious. std::mem_fn solve the issue reasonably well for member function pointers.Pun

© 2022 - 2024 — McMap. All rights reserved.