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

2

11

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.

Technology answered 17/12, 2015 at 8:54 Comment(9)
not is a keyword, hence unusableDambrosio
@DieterLücking okay, let's call it not_() then.Technology
Did you mean to return X_negate instead of X_predicate?Tilt
See the Page 497, books.google.co.in/…Styrax
@Tilt indeed ...Technology
@RahulTripathi good spot. The cppreference I quoted does in fact state that these functions are part of the standard until C++14, and hence by implications deprecated thereafter. So, I think, this answers it. Unfortunately, neither cppreference nor the source you're quoting gives an alternative.Technology
@Technology the alternative is lambdas.Porras
@Walter:- The very next page says: Being able to use lambda is really an improvement hereStyrax
Whereas an unified not_ would be possible in C++11 thanks to variadic template, I'm not sure if the traits to identify unary predicate is possible. What I get miss some case: DemoSuspensoid
B
12

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

Banda answered 17/12, 2015 at 9:17 Comment(7)
not is not a digraph, it is an "Alternative token"Pittsburgh
"that aren’t lexical keywords" - 'not' is a lexical keyword, so not a digraph.Pittsburgh
Shouldn't that be something like return ! this->pred(std::forward<A>(a)...);? You aren't inverting the result anywhere.Boulevardier
@anderas: oh, yes - I was focusing on getting the infrastructure right but didn't pay much attention to the actual logic (fixed)Propitiatory
std::unary_negate has an explicit constructor, other than your not_fn_t -- what is the rationale between this difference? Without explicit you can just return {std::forward<Pred>(pred)}; in not_fn(). Btw, you're returning a different type of not_fn_t in the return statement than promised in the function return type ...Technology
@Walter: actually, the code was quickly written to demonstrate how it could (roughly) be done: the return types should agree and the constructor should be explcit (and forwarding should happen, etc.). I'll fix soon... Thanks!Propitiatory
@Walter: I updated the answer to fix some of the issues (somehow I don't manage to forward the constructor properly but right now I can't look into why that is).Propitiatory
D
3

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.

Driver answered 17/12, 2015 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.