std::not_fn 是 C++17 引入的函数对象适配器,用于将任意可调用对象的返回值逻辑取反,专为传递给 STL 算法(如 std::find_if)设计,非简单替代 ! 操作符;C++20 起已弃用,推荐使用 std::ranges 替代方案。

std::not_fn 是什么,为什么不用 !std::is_integral_v
它不是简单的逻辑取反操作符,而是一个函数对象适配器,用来把任意可调用对象(比如 lambda、函数指针、函数对象)的返回值“翻转”。直接写 !func(x) 很自然,但当你需要把“取反后的行为”传给 std::find_if 或 std::remove_if 这类算法时,std::not_fn 才真正派上用场。
常见错误是以为它能替代 ! 去写条件判断——不能。它只在你需要一个“可调用物”时才有意义。
-
std::not_fn接收一个可调用对象,返回一个新的可调用对象,调用时自动对原结果做!操作 - 它会完美转发参数,支持右值引用和 const 限定,比手写 lambda 更泛化
- C++17 起才可用;C++20 开始,
std::not_fn已被标记为 deprecated(因为std::ranges提供了更清晰的替代方案),但目前仍完全可用
std::not_fn 的典型用法:配合 STL 算法
最常出现在 std::find_if_not 出现之前(C++17 以前)的兼容写法里,或者你想统一用 _if 系列而不换函数名时。
比如找第一个非数字字符:
立即学习“C++免费学习笔记(深入)”;
auto is_digit = [](char c) { return std::isdigit(static_cast<unsigned char>(c)); };
auto it = std::find_if(s.begin(), s.end(), std::not_fn(is_digit));
注意点:
- 必须显式传递 lambda 或函数对象,不能传函数调用表达式(如
std::not_fn(std::isdigit)错误:std::isdigit重载多,类型不明确) - 如果原函数接受
int,而你传char,std::not_fn不会帮你转型,错误发生在调用时,报错信息通常是 “no match for call”,不是一眼能看懂的 - 捕获变量的 lambda 可以安全传入
std::not_fn,它会保有原 lambda 的捕获语义
std::not_fn 和手写 lambda 哪个更好?
多数情况下,手写 lambda 更直观、调试更方便;std::not_fn 的优势在于泛化性和一致性——尤其当你要批量适配一组谓词时。
对比:
// 等价,但后者更短且无重复
std::not_fn([](int x) { return x > 5; })
[=](int x) { return !(x > 5); }
坑点:
- lambda 里写
!(x > 5)是编译期确定的;而std::not_fn包裹后,取反逻辑延迟到运行时调用,语义一致,但调试时栈帧多一层,断点可能跳进库实现 - 若原 callable 返回类型不是
bool(比如返回int或指针),std::not_fn仍按!规则转成bool,这和直接写!ptr行为一致,但容易忽略隐式转换 - 模板推导失败时(比如传了重载函数名),编译错误位置往往在
std::not_fn内部,不如 lambda 报错直接
std::not_fn 在 C++20 后还值得用吗?
新代码建议优先考虑 std::ranges::views::filter 配合直接写条件,或用 std::ranges::find_if_not 这类新算法——它们语义更直白,且避免了适配器嵌套。
但维护旧项目或需兼容 C++17 的场景下,std::not_fn 仍是干净的选择。
关键提醒:
-
std::not_fn不是语法糖,它有实际对象开销(极小,通常内联),但它的类型是唯一的,无法用auto多次捕获同一适配结果(两次调用std::not_fn(f)得到两个不同类型) - 它不处理异常传播逻辑——如果原 callable 抛异常,
std::not_fn一样抛,不会吞掉 - 最容易被忽略的是:它对
noexcept的传播是保守的;即使原 callable 是noexcept,std::not_fn包裹后默认不标记为noexcept,可能影响某些优化路径











