Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why are there std::not1() and std::not2() rather than a single overloaded std::not_()?

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.

like image 318
Walter Avatar asked Dec 17 '15 08:12

Walter


2 Answers

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).

like image 61
Dietmar Kühl Avatar answered Oct 21 '22 11:10

Dietmar Kühl


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.

like image 27
ecatmur Avatar answered Oct 21 '22 11:10

ecatmur