Override a lambda without knowing the type of the argument?

I am trying to write an in-place filter function that works similarly to a Python filter. For example:

std::vector<int> x = {1, 2, 3, 4, 5};
filter_ip(x, [](const int& i) { return i >= 3; });
// x is now {3, 4, 5}

      

I tried this first:

template <typename Container, typename Filter>
void filter_ip(Container& c, Filter&& f)
{
  c.erase(std::remove_if(c.begin(), c.end(), std::not1(f)), c.end());
}

      

However, this doesn't work because lambdas doesn't have a fieldargument_type

.

This next option works :

template <typename Container, typename Filter>
void filter_ip(Container& c, Filter&& f)
{
  c.erase(std::remove_if(c.begin(), c.end(), 
                         [&f](const typename Container::value_type& x) { 
                            return !f(x); 
                         }), 
          c.end());
}

      

However, it seems less ideal, because in the past he would have required to Container

have begin

, end

and erase

, as it now also requires that he defined a value_type

. Plus it looks a bit cumbersome.

This is the second approach in this answer . The first one would use std::not1(std::function<bool(const typename Container::value_type&)>(f))

instead of a lambda, which still requires a type.

I also tried to specify arg func as std::function

with a known argument type:

template <typename Container, typename Arg>
void filter_ip(Container& c, std::function<bool(const Arg&)>&& f)
{
  c.erase(std::remove_if(c.begin(), c.end(), std::not1(f)), c.end());
}

      

But then I get:

'main()::<lambda(const int&)>' is not derived from 'std::function<bool(const Arg&)>'

      

Is there a way to get around this? Intuitively, it seems like it should be really easy, because all you have to do is apply to a non-bool you already know. f

returns.

+3


source to share


3 answers


It seems to me that if you already require begin

, end

and erase

also require value_type

, this is a rather small addition. If you manage to refuse the request erase

, it will bring you at least a few real containers, but eliminating the requirement for value_type

will fail.

However, if you had a container that did define erase

, but not value_type

, you could direct the requirement that it define value_type

directly by getting the value_type from the iterator:

template <typename Container, typename Filter>
void filter_ip(Container& c, Filter&& f) {
    using It = decltype(c.begin());

    c.erase(std::remove_if(c.begin(), c.end(),
        [&f](const std::iterator_traits<It>::value_type& x) {
        return !f(x);
    }),
        c.end());
}

      

By using iterator_traits<T>::value_type

, you can (for example) get the pointee type when the iterator is indeed a pointer. I don't know of any practical advantages in this case, although when you already require begin()

, end()

and (especially) erase

. We could eliminate the requirement for begin()

and end()

as members with std::begin(c)

and std::end(c)

, but (again) that doesn't really give us anything meaningful (like being able to work with arrays) when we still need a member erase

.

An even simpler approach is to use std::partition

:

template <typename Container, typename Filter>
void filter_ip(Container& c, Filter&& f) {
    c.erase(std::partition(c.begin(), c.end(), f), c.end());
}

      



This has the disadvantage that it can (will) reorder the elements it stores, so it won't work if you really need to keep the original order. It can also be less efficient if copying / moving the construct is much cheaper than replacing it (but this is rather unusual).

One final possibility would be to simply implement the algorithm yourself, instead of delegating to another algorithm:

template <typename Container, typename Filter>
void filter2(Container& c, Filter&& f) {
    auto dst = c.begin();

    for (auto src = dst; src != c.end(); ++src)
        if (f(*src)) {
            *dst = *src;
            ++dst;
        }
    c.erase(dst, c.end());
}

      

If you'd rather avoid self-assignment, you can add:

while (f(*dst))
    ++dst;

      

... before the loop for

above.

+1


source


If you can't use C ++ 14 generic lambdas, how about delegating to a classic functor with a template operator()

:

#include <utility>
#include <vector>
#include <algorithm>
#include <iostream>

template <class F>
struct negate {
    negate(F&& f)
    : _f(std::forward<F>(f)) {}

    template <class... Args>
    bool operator () (Args &&... args) {
        return !_f(std::forward<Args>(args)...);
    }

private:
    F _f;
};

template <typename Container, typename Filter>
void filter_ip(Container& c, Filter&& f)
{
    c.erase(std::remove_if(
        c.begin(),
        c.end(),
        negate<Filter>(std::forward<Filter>(f))),
        c.end()
    );
}

int main() {
    std::vector<int> v {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    filter_ip(v, [](int i) {return bool(i%2);});
    for(auto &&i : v)
        std::cout << i << ' ';
    std::cout << '\n';
}

      

Output:



1 3 5 7 9 

      

Live on coliru

+5


source


template<class F>
struct not_f_t {
  F f;
  template<class...Ts>
  decltype(!std::declval<typename std::result_of<F&(Ts...)>::type>())
  operator()(Ts&&...ts) {
    return !f(std::forward<Ts>(ts)...);
  }
};
template<class F, class dF=typename std::decay<F>::type>
not_f_t<dF> not_f(F&& f){
  return {std::forward<F>(f)};
}

      

or in C ++ 14, we can drop the class not_f_t

and do:

template<class F,class dF=std::decay_t<F>>// dF optional
auto not_f(F&& f){
  return [f=std::forward<F>(f)](auto&&...args)mutable
  ->decltype(!std::declval<std::result_of_t<dF&(decltype(args)...)>>()) // optional, adds sfinae
  {
    return !f(decltype(args)(args)...);
  };
}

      

and then, because it chunks:

template<class C, class F>
void erase_remove_if( C&& c, F&& f ) {
  using std::begin; using std::end;
  c.erase( std::remove_if( begin(c), end(c), std::forward<F>(f) ), end(c) );
}

      

we get:

std::vector<int> x = {1, 2, 3, 4, 5};
erase_remove_if(x, not_f([](int i){return i>=3;}));

      

+2


source







All Articles