The C++ std
namespace contains the helper functions std::not1
and std::not2
. They both take a unary or binary predicate functor, respectively, and return a std::unary_negate
or std::binary_negate
predicate, respectively.
I was wondering whether it should not be possible using some template magic to have
template<typename Predicate> inline
enable_if_t<is_unary_predicate<Predicate>::value, unary_negate<Predicate> >
not_(Predicate const&pred)
{ return unary_negate<Predicate>{pred}; }
template<typename Predicate> inline
enable_if_t<is_binary_predicate<Predicate>::value, binary_negate<Predicate> >
not_(Predicate const&pred)
{ return binary_negate<Predicate>{pred}; }
which distinguishes the argument pred
passed to return the appropriate predicate. Of course, there are the odd cases where the passed object pred
has both types of operators (unary and binary), when this will not work, but these can be dealt without the use of this helper function.
Working out the correct overload without C++11 capabilities isn't entirely trivial. When the STL was designed and these function objects were proposed, there wasn't even a compiler capable of compiling some of these functions. As a result, some of the functions are harder to use than could have been. For example, it would have been entirely viable to create a std::not_()
function (std::not()
isn't possible because not
happens to be a alternative token and as such isn't a viable function name). That is, the answer is: it is mostly a historical accident.
It is quite possible that std::not1
and std::not2
were proposed at a time when the rules for partial ordering of function overloads were still rather muddy. The main proposal for STL was done in 1994 or 1995 (I couldn't locate it quickly in the mailing archive). I wouldn't be surprised if the overload rules were actually changed in response to the STL proposal.
That said, it took a couple of years before others got up to speed and developed improved versions of these interfaces. Boost was spearheading these developments.
With respect to implementation magic, it might actually be quite simple to create a not_
function working with all sorts of arities:
template <typename Pred>
class not_fn_t {
std::decay_t<Pred> pred;
public:
explicit not_fn_t(Pred p): pred(p) {}
template <typename... A>
bool operator()(A&&... a) const {
return !this->pred(std::forward<A>(a)...);
}
};
template <typename Pred>
not_fn_t<Pred> not_fn(Pred&& pred) {
return not_fn_t<Pred>(std::forward<Pred>(pred));
}
Actually, this is pretty much what was voted into the C++ Working Paper as std::not_fn()
at the last meeting. This is a C++11 formulation but the moral equivalent could have been done with earlier versions of C++ with just expanding the function call operator for each supported arity (and, obviously, without perfect forwarding).
There was a proposal along these lines in 2013: N3699 A proposal to add a generalized callable negator. This has bumped along for some time (most recent version is N4022) and looks like it should make it into the second Library Fundamentals TS; it's present in section func.not_fn in Library Fundamentals TS 2 draft n4564.
The reason not1
and not2
exist in the standard is that they have been there for quite some time, since before the metaprogramming techniques existed that would be necessary to support a single negator.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With