std::not_fn比手写lambda更可靠,因其正确处理参数转发、cv限定符、SFINAE及void返回值限制,并自动适配成员函数和完美转发;但不支持返回void的可调用对象、短生命周期捕获lambda或未取址的函数名。

std::not_fn 为什么比手写 lambda 更可靠?
std::not_fn 不是简单地给函数加个 !,它解决的是“可调用对象包装后正确转发参数并处理返回值”的一整套问题。比如你传入一个重载函数、成员函数指针或带引用捕获的 lambda,手写 [&](auto&&... args){ return !f(std::forward<decltype(args)>(args)...); } 很容易在 cv 限定符、引用折叠、SFINAE 友好性上出错。
- 手写取反 lambda 在面对
std::function<void()>或返回void的可调用对象时会编译失败(!void不合法),而std::not_fn明确禁止包装返回void的调用,报错更早、更明确 - 它自动适配
const/volatile成员函数,比如包装std::string::empty这种 const 成员函数时,std::not_fn(&std::string::empty)能正确推导调用签名 - 对完美转发支持更健壮:内部使用
std::invoke,能处理绑定器(如std::bind)、成员指针、普通函数指针等所有标准可调用类型
示例:你想筛选非空字符串,直接用 std::not_fn(&std::string::empty),而不是 [](const std::string& s){ return !s.empty(); } —— 后者无法泛化到 std::vector<:string></:string> 的 const 迭代器解引用场景(因类型不匹配)。
std::not_fn 包装哪些东西会出问题?
不是所有可调用对象都能安全喂给 std::not_fn,常见翻车点:
- 返回
void的函数或 lambda:编译错误,提示类似invalid operands to binary expression ('void' and 'bool') - 捕获了局部变量的 lambda,且该变量生命周期短于
std::not_fn对象本身:运行时未定义行为(和普通 lambda 一样,但容易被忽略) - 函数名未加取地址符,比如
std::not_fn(foo)(foo是函数名)会触发模板实参推导失败;必须写成std::not_fn(&foo)或用static_cast强制指定类型 - 包装一个返回非布尔类型(如
int)的谓词时,std::not_fn仍按!x解释,即隐式转为bool再取反 —— 这符合预期,但要注意和显式比较(如x != 0)语义是否一致
典型错误写法:std::not_fn([]{ std::cout → 编译不过;正确做法是别包装 <code>void 返回值,或先用 lambda 转成返回 bool 的版本。
立即学习“C++免费学习笔记(深入)”;
和 std::negate、operator! 重载混用时的优先级陷阱
std::not_fn 和传统函数对象(如 std::negate<int></int>)或自定义 operator! 容易混淆:
-
std::negate只适用于算术类型取负(-x),不是逻辑取反;误用std::not_fn(std::negate<>{})会导致对 bool 做-true,结果仍是true(因为-1隐式转bool为true) - 如果类重载了
operator!,std::not_fn(obj)会调用该重载;但如果类没重载,又没提供operator bool(),则std::not_fn包装后调用会失败(!obj不合法) - 和
std::logical_not不同:std::logical_not是仿函数模板,只能用于单参数且要求参数可转bool;std::not_fn更通用,但底层仍依赖!运算符是否可用
建议:除非明确需要适配已有重载的 operator!,否则优先用 std::not_fn 包装返回 bool 的谓词;避免和 std::negate 混用,二者语义完全不同。
在 STL 算法中嵌套使用 std::not_fn 的实际写法
最常见需求是组合多个条件,比如“不满足 A 且不满足 B”,这时不能直接写 std::not_fn(A && B)(C++ 中没有 operator&& 重载支持这种写法),得手动组合:
- 正确方式是包装单个谓词后传入算法:
std::remove_if(v.begin(), v.end(), std::not_fn(is_valid)) - 多条件组合推荐用 lambda:
std::all_of(v.begin(), v.end(), [&](const auto& x){ return !pred1(x) && !pred2(x); });不要试图用std::not_fn去包另一个std::not_fn,那只是多套一层,无实际意义 - 和
std::bind配合时注意:先std::bind再std::not_fn,顺序不能反;例如std::not_fn(std::bind(&Widget::is_ready, _1))是合法的,反过来则无法编译
性能上无额外开销:std::not_fn 是零成本抽象,生成的代码和手写等效 lambda 几乎一致;但它的真正价值在于类型安全和语义清晰——当你看到 std::not_fn(f),你就知道它一定是个可调用对象的逻辑取反,且编译器能帮你挡住大部分误用。
复杂点在于它不解决“怎么定义谓词”,只解决“怎么安全取反”。很多人卡在第一步:传进去的东西根本就不是标准谓词(比如返回 int 或 void),这时候 std::not_fn 不会迁就你,它会立刻报错。











