std::not_fn是支持完美转发的谓词取反包装器,能适配任意可调用对象(lambda/函数指针/functor/成员函数),但不支持重载函数名、void返回值或默认参数,泛型场景下比lambda更轻量透明。

std::not_fn 本质是包装器,不是简单取反
它把原谓词的调用结果用 !operator() 包一层,但关键在于:它会**自动适配参数转发方式**,解决 std::not1/std::not2 那套老接口的绑定僵化问题。比如你有个自由函数 is_even(int),直接传给 std::not1 会编译失败——因为它要求可调用对象必须继承自 std::unary_function,而 C++11 后这根本不是强制约定。
-
std::not_fn不关心你传的是 lambda、函数指针、成员函数指针,还是重载了operator()的 functor - 它内部用完美转发处理参数,支持任意数量和类型的实参(包括右值)
- 别试图对返回
void的可调用对象用std::not_fn——!void无意义,编译不过
常见错误:误以为 std::not_fn 能直接作用于重载函数名
写 std::not_fn(std::isalnum) 看似合理,但 std::isalnum 是重载函数族(int(int) 和 bool(unsigned short) 等),编译器无法推导具体选哪个,报错类似:error: reference to overloaded function could not be resolved。
- 必须显式转型或用 lambda 消除歧义,例如:
std::not_fn([](int c) { return std::isalnum(c); }) - 对
std::string::empty这类成员函数,不能直接写std::not_fn(&std::string::empty)——缺少对象实例,得配合std::bind或用 lambda 捕获 - 若原函数有默认参数,
std::not_fn不会自动继承;需在 lambda 中显式提供
与 lambda 手写取反对比:何时该用 std::not_fn
写 [](auto&& x) { return !pred(x); } 很直观,但 std::not_fn 在泛型上下文里更轻量、更透明——它不引入新闭包类型,模板推导更干净,且某些标准算法(如 std::ranges::remove_if)内部可能对谓词做额外优化,而 lambda 可能打断这种优化路径。
- 当你在模板函数中接收谓词参数,并希望保持类型擦除简洁时,优先用
std::not_fn(pred) - 如果需要捕获局部变量或做额外计算(比如先转换再判断),必须用 lambda,
std::not_fn不支持捕获 - 性能上无实质差异,但
std::not_fn对移动语义更友好:它转发参数时不强制拷贝
实际组合场景:嵌套 not_fn 容易引发可读性灾难
像 std::not_fn(std::not_fn(pred)) 理论上等价于 pred,但没人这么写——既难读又没收益。更现实的问题是链式组合其他适配器,比如 std::not_fn(std::bind_front(func, arg))。
立即学习“C++免费学习笔记(深入)”;
- 组合层级超过两层就该考虑提取成具名变量,否则调试时栈帧里全是未命名模板实例
- 注意
std::not_fn返回类型是未指定的实现相关类,不能跨模块传递(比如作为 DLL 导出函数参数) - 在 C++17 前没有
std::not_fn,部分旧项目用宏模拟,行为不一致;升级时要检查所有谓词组合点
真正麻烦的从来不是怎么写 std::not_fn,而是当它嵌在三层 std::ranges::filter_view 里,而某个底层谓词突然抛异常时,你得花十分钟在模板展开堆栈里定位到哪一层的 ! 操作触发了空指针解引用。








