在不知道参数类型的情况下对lambda求反

Negate a lambda without knowing the argument type?

本文关键字:lambda 求反 情况下 不知道 参数 类型      更新时间:2023-10-16

我正在尝试编写一个与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没有argument_type字段。

以下变体确实有效:

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。另外,它看起来有点笨重。

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

我还尝试将arg函数指定为具有已知参数类型的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&)>'

有办法解决这个问题吗?从直觉上看,它似乎应该非常简单,因为您所需要做的就是将一个not应用于您已经知道f返回的bool。

如果不能使用C++14通用lambda,那么用模板化的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';
}

输出:

1 3 5 7 9 

在Coliru 上直播

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

在我看来,如果您已经需要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时,我不知道在这种情况下有什么实际优势。我们可以通过使用std::begin(c)std::end(c)来消除对begin()end()作为成员的要求,但(再次(当我们仍然需要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;

在上述CCD_ 31循环之前。