我正在尝试编写一个与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
begin
,end
和erase
,而现在它还需要它定义了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。
答案 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';
}
答案 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)
在我看来,如果您已经需要begin
,end
和erase
,那么还需要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
循环之前。