在不知道参数类型的情况下否定lambda?

时间:2015-06-02 15:06:30

标签: c++ templates c++11 types lambda

我正在尝试编写一个与Python的过滤器类似的就地过滤器功能。例如:

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

首先我尝试了这个:

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());
}

但是,这不起作用,因为lambdas don't have an argument_type field

以下变体does work

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());
}

然而,它似乎不太理想,因为之前,它只需要Container beginenderase,而现在它还需要它定义了value_type。此外,它看起来有点笨拙。

这是this answer中的第二种方法。第一个将使用std::not1(std::function<bool(const typename Container::value_type&)>(f))而不是lambda,它仍然需要类型。

我还尝试将arg func指定为具有已知参数类型的std::function

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());
}

但后来我得到了:

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

这有什么办法吗?直观地说它似乎应该非常简单,因为您需要做的就是将一个不应用于您已经知道的f返回的bool。

3 个答案:

答案 0 :(得分:5)

如果你不能使用C ++ 14泛型lambdas,那么如何委托给带有模板blah blah durrr user entry user entry 23425253 user entry something something who cares blah blah durrr user entry user entry 23425253 user entry something something who cares 的经典仿函数:

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';
}

Live on Coliru

答案 1 :(得分:2)

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)};
}

或在C ++ 14中,我们可以免除not_f_t类并执行:

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)...);
  };
}

然后,因为它摇滚:

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) );
}

我们得到:

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

答案 2 :(得分:1)

在我看来,如果您已经需要beginenderase,那么还需要value_type是一个非常小的补充。如果你可以避免要求erase,那么至少可以获得一些真正的容器,但是消除对value_type的要求并没有取得多大成就。

尽管如此,如果您的容器确实定义了erase,而不是value_type,那么您可以通过获取value_type来直接定义value_type的要求。来自迭代器:

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());
}

使用iterator_traits<T>::value_type,您可以(例如)在迭代器确实是指针时获取指针类型。虽然您已经需要begin()end()和(特别是)erase,但我在这种情况下并不知道任何实际优势。我们可以使用begin()end()来消除std::begin(c)std::end(c)作为成员的要求,但是(再次)我们并没有真正获得任何有意义的内容(例如当我们仍然需要erase成员时,能够使用数组。

更简单的方法是使用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());
}

这样做的缺点是它可以(将)重新排列它保留的元素,因此如果你真的需要保留原始顺序,它就不会工作。如果复制/移动构造比交换便宜得多(但这种情况相当罕见),这也可能效率较低。

最后一种可能性就是自己实施算法,而不是委托给另一种算法:

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());
}

如果您希望避免自行分配,可以添加:

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

...在上面for循环之前。