如何基于已有谓词定义C++通用not高阶函数?
实现通用的逻辑否定高阶函数及扩展方案
首先得说,你的需求非常合理——谁都不想每次过滤都写重复的否定lambda,而且能扩展到其他逻辑操作就更方便了。咱们一步步解决这个问题:
先解决核心问题:写一个通用的not_fn高阶函数
你之前的实现踩了几个坑:用std::function带来类型擦除开销、引用捕获导致悬空、还误用了C++关键字not(这是保留字,不能当函数名)。这里给你一个更通用、高效的版本:
#include <utility> // 用于std::forward和std::move template <typename Predicate> auto not_fn(Predicate pred) { // 移动捕获谓词,避免不必要的拷贝(尤其是大的函数对象) return [pred = std::move(pred)](auto&&... args) { // 完美转发参数,适配任意签名的谓词 return !pred(std::forward<decltype(args)>(args)...); }; }
这个版本的优势:
- 不需要
std::function,没有类型擦除的性能损耗 - 支持任意签名的谓词(不止
bool(const MyType*),比如带多个参数、成员函数指针都可以) - 完美转发参数,保证谓词接收到的参数类型和值类别正确
- 移动捕获避免拷贝开销,更高效
你的使用场景可以改成这样(注意:remove_if只是把元素移到容器末尾,必须配合erase才会真正删除元素):
#include <algorithm> #include <vector> using namespace std; vector<const MyType*> elems = ...; // 删除所有匹配my_pred的元素,剩下的就是不匹配的 elems.erase(remove_if(begin(elems), end(elems), not_fn(my_pred)), end(elems));
为什么你的原实现行不通?
咱们拆解下你写的代码问题:
template<typename T> function<bool(T)> not(function<bool(T)> pred) { return [&](T x) { return !pred(x) }; }
- 关键字冲突:
not是C++的保留关键字,不能用作函数名,这是编译报错的直接原因之一 - 悬空引用:
[&]捕获的是函数参数pred的引用,函数执行完毕后pred会被销毁,返回的lambda里的引用就变成悬空的,调用时会触发未定义行为 - 通用性差:只能接受
std::function<bool(T)类型的谓词,像普通函数指针、lambda这些无法隐式转换的类型就用不了,而且只能处理单参数的谓词
关于std::logical_not的正确打开方式
你之前用std::logical_not失败,是因为它默认是用来否定值,而不是包装谓词。如果一定要用它,可以结合std::bind,但写法很繁琐:
#include <functional> using namespace std::placeholders; elems.erase(remove_if(begin(elems), end(elems), std::bind(std::logical_not<bool>(), std::bind(my_pred, _1))), end(elems));
显然,这种写法远不如咱们自己写的not_fn简洁直观。
扩展到其他逻辑操作符
既然你想扩展到与、或等操作,咱们可以用同样的模板思路实现:
逻辑与(同时满足两个谓词)
template <typename Pred1, typename Pred2> auto and_fn(Pred1 pred1, Pred2 pred2) { return [pred1 = std::move(pred1), pred2 = std::move(pred2)](auto&&... args) { return pred1(std::forward<decltype(args)>(args)...) && pred2(std::forward<decltype(args)>(args)...); }; }
逻辑或(满足任意一个谓词)
template <typename Pred1, typename Pred2> auto or_fn(Pred1 pred1, Pred2 pred2) { return [pred1 = std::move(pred1), pred2 = std::move(pred2)](auto&&... args) { return pred1(std::forward<decltype(args)>(args)...) || pred2(std::forward<decltype(args)>(args)...); }; }
用法示例:比如保留既不匹配my_pred也不匹配another_pred的元素:
// 删除匹配任意一个谓词的元素 elems.erase(remove_if(begin(elems), end(elems), or_fn(my_pred, another_pred)), end(elems));
C++20及以上的更优雅方案
如果你用的是C++20或更高版本,标准库已经帮你做好了这些:
- 标准库自带
std::not_fn,直接用就行,不用自己实现 - 用
std::erase_if可以一步完成“删除匹配谓词的元素”,不用写erase+remove_if:
#include <vector> std::erase_if(elems, my_pred); // 直接删除所有匹配my_pred的元素,剩下的就是你要的
- 还可以用范围库的
std::views::filter直接生成过滤后的视图:
#include <ranges> // 生成过滤后的视图(不拷贝元素,只是视图) auto filtered_view = elems | std::views::filter(std::not_fn(my_pred)); // 如果需要转换成vector: std::vector<const MyType*> filtered_elems(filtered_view.begin(), filtered_view.end());
内容的提问来源于stack exchange,提问作者Gaston Simone
相关产品推荐
相关产品推荐

