views:

234

answers:

3

Consider the following non-working code:

typedef map<int, unsigned> mymap;
mymap m;
for( int i = 1; i < 5; ++i )
    m[i] = i;
// 'remove' all elements from map where .second < 3
remove_if(m.begin(), m.end(), bind2nd(less<int>(), 3));

I'm trying to remove elements from this map where .second < 3. This obviously isn't written correctly. How do I write this correctly using:

  1. Standard STL function objects & techniques using bind + less<> but without having to write a custom functor
  2. Boost.Bind
  3. C++0x Lambdas

I know I'm not eraseing the elements. Don't worry about that; I'm just simplifying the problem to solve.

+2  A: 

I'm not sure how to do this using just the STL binders but I think your main problem is that what's being passed into the functor you give to remove isn't just an int but a pair<int, unsigned>.

Using boost::bind you'd do it like this:

remove_if(m.begin(), m.end(), bind(&std::pair<int, unsigned>::second, _1) < 3);

Using a lambda function it's something like this:

remove_if(m.begin(), m.end(), [](const std::pair<int, unsigned>& p) { return p.second < 3; } );

I haven't checked that this compiles, sorry.

Alex - Aotea Studios
I could not get any of the above two to compile. Seems like internal shuffling of pairs done during the remove_if algorithm are not liked by the compiler. The pair::first is a const int to which you can't assign.
Sumant
You're right, I didn't consider the container type. remove_if doesn't work on associative containers. I'm pretty sure what I wrote would work for a vector though.
Alex - Aotea Studios
Great answer using boost bind. I use the `!` operator with binds frequently, but often forget that the other operators are overloaded as well.
caspin
+1  A: 

Although I could not get the remove_if algorithm to work for the reasons mentioned above, I got count_if algorithm to work with somewhat elaborate functor definitions and compositions. These are not defined in the standard but they are inspired from what is available in SGI STL.

template <class Pair>
struct select2nd : std::unary_function<Pair, typename Pair::second_type>
{
  typedef std::unary_function<Pair, typename Pair::second_type> super;
  typedef typename super::result_type result_type;
  typedef typename super::argument_type argument_type;

  result_type & operator ()(argument_type & p) const {
    return p.second;
  }
  result_type const & operator ()(argument_type const & p) const {
    return p.second;
  }
};

template <class UnaryFunc1, class UnaryFunc2>
struct unary_compose : std::unary_function<typename UnaryFunc2::argument_type,
                                           typename UnaryFunc1::result_type>
{
  typedef std::unary_function<typename UnaryFunc2::argument_type,
                              typename UnaryFunc1::result_type> super;
  typedef typename super::result_type result_type;
  typedef typename super::argument_type argument_type;

  UnaryFunc1 func1_;
  UnaryFunc2 func2_;
  unary_compose(UnaryFunc1 f1, UnaryFunc2 f2) : func1_(f1), func2_(f2) {}
  result_type operator () (argument_type arg) {
    return func1_(func2_(arg));
  }
};

template <class UnaryFunc1, class UnaryFunc2>
unary_compose<UnaryFunc1, UnaryFunc2>
compose1(UnaryFunc1 f1, UnaryFunc2 f2) {
  return unary_compose<UnaryFunc1, UnaryFunc2>(f1,f2);
};

int main(void) {
  typedef std::map<int, unsigned> mymap;
  mymap m;
  for(int i = 0; i < 5; ++i )
    m[i] = i;

  std::cout << "Count = "
            << std::count_if(m.begin(), m.end(),
               compose1(std::bind2nd(std::less<int>(), 3), select2nd<mymap::value_type>()))
            << std::endl;
}
Sumant
Well, the question says *"without having to write a custom functor"*. If any custom functors are allowed its trivial.
Georg Fritzsche
+2  A: 

remove_if will not work with associative containers. But remove_copy_if may work but at the expense of copying your map. Instead I'll do it with count_if.

1) Standard STL function objects & techniques using bind + less<> but without having to write a custom functor

// I don't think it can be done with standard c++ without introducing new functors and adaptors.
std::size_t count = std::count_if( m.begin(), m.end(),
      std::sgi::compose1(
         std::bind_2nd( std::less<int>(), 3 ),
         &boost::get<1,mymap::value_type> ) );

2) Boost.Bind

std::size_t count = std::count_if( m.begin(), m.end(),
      boost::compose_f_gx(
         &boost::bind( std::less<int>, _1, 3 )
         &boost::get<1,mymap::value_type> ) );

3) C++0x Lambdas

std::size_t count = std::count_if( m.begin(), m.end(),
      []( const mymap::value_type& item )
         { return item.second < 3; } );

If you really want remove_if behavior you'll need to roll your own algorithm. I don't believe there are any modifying standard algorithms that work with associative containers.

template< typename FwdIter, typename AssocCont, typename Pred >
std::size_t assoc_remove_if( FwdIter iter, FwdIter end, AssocCont& cont, Pred pred )
{
   std::size_t count = 0;
   while( iter != end )
   {
      if( pred(*iter) )
      {
         ++count;
         iter = cont.erase(iter);
      }
      else
      {
         ++iter;
      }
   }
   return count;
}
caspin
Boost.Compose has been deprecated and removed from Boost - or am i overlooking `compose_f_gx` in another module? *AFAIK* we are now supposed to use nested binds: http://www.boost.org/doc/libs/1_42_0/libs/bind/bind_as_compose.cpp
Georg Fritzsche
Good call, I hadn't ever used the compose stuff in production code so I never looked at it again. Nested binds are much more elegant.
caspin