How std::not_fn Creates Negated Callables in C++
Introduced in C++17 and refined in C++20, std::not_fn is
a standard library utility that wraps any callable entity to return a
new function object representing its logical negation. This article
explores how std::not_fn works under the hood, how it
leverages modern C++ features like perfect forwarding and
std::invoke to construct negated callables, and how updates
in C++20 enhance its usability with compile-time execution and value
category forwarding.
The Purpose of
std::not_fn
Prior to C++17, negating predicates required clumsy utilities like
std::not1 and std::not2. These utilities
required the target callable to define specific nested types (such as
argument_type or first_argument_type), making
them incompatible with raw lambdas and arbitrary callables without extra
boilerplate like std::ptr_fun.
std::not_fn, defined in the
<functional> header, eliminates these constraints. It
accepts any callable target—lambdas, free functions, member pointers, or
custom functors—and constructs a wrapper object that inverts the boolean
result of invoking the target.
Internal Mechanics and
std::invoke
When calling std::not_fn(f), it constructs and returns
an unspecified call wrapper object containing a decayed copy of the
callable f. The wrapper's call operator
(operator()) forwards incoming arguments directly to the
stored callable using std::invoke.
The core operation inside the returned wrapper can be conceptually modeled as:
template <typename F>
class negated_callable {
F f;
public:
template <typename... Args>
auto operator()(Args&&... args) &
-> decltype(!std::invoke(f, std::forward<Args>(args)...)) {
return !std::invoke(f, std::forward<Args>(args)...);
}
template <typename... Args>
auto operator()(Args&&... args) const &
-> decltype(!std::invoke(f, std::forward<Args>(args)...)) {
return !std::invoke(f, std::forward<Args>(args)...);
}
template <typename... Args>
auto operator()(Args&&... args) &&
-> decltype(!std::invoke(std::move(f), std::forward<Args>(args)...)) {
return !std::invoke(std::move(f), std::forward<Args>(args)...);
}
template <typename... Args>
auto operator()(Args&&... args) const &&
-> decltype(!std::invoke(std::move(f), std::forward<Args>(args)...)) {
return !std::invoke(std::move(f), std::forward<Args>(args)...);
}
};Because std::invoke is used, std::not_fn
seamlessly handles member object and member function pointers in
addition to standard callable entities.
Perfect Forwarding and Value Categories
std::not_fn ensures zero-cost abstraction through
rigorous use of perfect forwarding:
- Argument Forwarding: Arguments passed to the negated callable wrapper are forwarded to the wrapped target without redundant copying or moving.
- Callable Value Categories: Modern implementations
provide reference-qualified overloads (
&,const &,&&, andconst &&). This allows the wrapper to be invoked on rvalues, enabling moves if the underlying callable supports move-only invocations.
Enhancements in C++20
C++20 significantly improved std::not_fn:
constexprSupport: The wrapper object returned bystd::not_fncan be evaluated at compile time if the underlying callable and its invocation are valid constant expressions.- Conditional
noexcept: The call operators are markednoexceptwhenever the correspondingstd::invokeexpression combined with logical negation does not throw.
Practical Example
In standard algorithm pipelines, std::not_fn allows
existing predicates to be reversed inline without writing boilerplate
lambda wrappers:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
bool is_even(int n) {
return n % 2 == 0;
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
// Erase all numbers that are NOT even
numbers.erase(
std::remove_if(numbers.begin(), numbers.end(), std::not_fn(is_even)),
numbers.end()
);
// Output: 2 4 6
for (int n : numbers) {
std::cout << n << " ";
}
}Through type decay, universal references, and standard invocation
mechanisms, std::not_fn produces robust, negated function
objects with minimal syntax and optimal execution characteristics.