A proposal to add a generalized callable negator (Revision 1)
Document number:   N3800
Date:   2013-09-16
Project:   Programming Language C++, Library Working Group
Reply-to:  
Tomasz Kamiński <tomaszkam at gmail dot com>

A proposal to add a generalized callable negator (Revision 1)

Introduction

This proposal is to add function template not_fn that will allow to create negator of any callable object.

Revision history

Changes since N3699:

Motivation and Scope

The standard negators not1 and not2 accept only unary and binary functors that define argument_type or first_argument_type and second_argument_type respectively, which make them unusable with results of standard library functions such as bind and mem_fn. Furthermore, with relation to N3421, they cannot be used with new operator functor specializations.

This proposal addresses the problem by introducing a template function not_fn that returns complement of arbitrary predicate.

Comparison with lambda

While the polymorphic lambda expressions (N3649) may be used to negate arbitrary functor, the proposed function offers more compact syntax:

  std::partition(v.begin(), v.end(), [f](auto& p) { return !f(p); });
  std::partition(v.begin(), v.end(), std::not_fn(f));

Furthermore, the use of lambda expression requires separate treatment of member pointers:

  std::partition(v.begin(), v.end(), [pm](auto& p) { return !std::mem_fn(pm)(p); });
  std::partition(v.begin(), v.end(), std::not_fn(pm));

Deprecation

With the incorporation of proposed functionality the old standard negators should be deprecated.

Design Decisions

New specializations of unary_negate, binary_negate

Problem addressed by this paper may be solved by introducing perfect forwarding specializations of unary_negate and binary_negate for types that do not define argument_type and first_argument_type, second_argument_type nested types respectively, without breaking existing code. Although this solution does not address functions with arbitrary number of arguments and requires additional implementation burden.

Return type

The perfect forwarding of return type was chosen instead of hard-coded bool. Similar argumentation to the one provided in N3421 applies.

Argument typedefs

The argument_type, first_argument_type and second_argument_type nested types are required by implemenatation to be consistent with most of library functors, that define these types where they are well defined for target callable type. Futhermore it is increasing the amount of existing code that will remain valid after changing not1, not2 to not_fn.

Impact On The Standard

This proposal has no dependencies beyond a C++11 compiler and Standard Library implementation. (It depends on perfect forwarding, decltype and trailing return types.)

Nothing depends on this proposal.

Proposed wording

After the declaration of not2 in the section 20.10 [function.objects]/2 (Header <functional> synopsis), add:

  // 20.10.9, negators
  template <class F> unspecified not_fn(F&& f);

After paragraph 20.10.8 Negators [negators], insert a new paragraph. (Chapter [bind] (Function template bind) becomes 20.10.?)

20.10.9 Function template not_fn [not_fn]

  template <class F>
    unspecified not_fn(F&& f);

In the text that follows, the following names have the following meanings:

  • FD is the type decay<F>::type,
  • fd is an lvalue of type FD constructed from std::forward<F>(f),
  • fn is a simple call wrapper created as a result of not_fn(f),
  • FN is a type of fn,
  • FW is a type of forwarding call wrapper for f with a weak result type ([func.require] 20.10.2).

Requires:
is_constructible<FD, F>::value shall be true. f shall be a callable object ([func.require] 20.10.2).

Returns:

A simple call wrapper fn such that the expression fn(a1, a2, ..., aN) is equivalent to !INVOKE(fd, a1, a2, ..., aN) ([func.require] 20.10.2).

Throws:

Nothing unless the construction of fd throws an exception.

Remarks:

The return type shall satisfy the requirements of MoveConstructible. If FD satisfy the requirements of CopyConstructible, then the return type shall satisfy the requirements of CopyConstructible. [ Note: This implies that FD is MoveConstructible. — end note ]

The FN shall define a nested type named result_type as a synonym to R only if FW would have a nested type result_type; the type R is decltype(!declval<typename FW::result_type>()).

The FN shall define a nested type named argument_type as a synonym for T1 only if the type F is any of the following:

  • a function type or a pointer to function type taking one argument of type T1
  • a pointer to member function R T0::f() cv (where cv represents the member function's cv-qualifiers); the type T1 is cv T0*
  • a class type with a member type argument_type; the type T1 is F::argument_type..

The FN shall define two nested types named first_argument_type and second_argument_type as synonyms for T1 and T2, respectively, only if the type F is any of the following:

  • a function type or a pointer to function type taking two arguments of types T1 and T2
  • a pointer to member function R T0::f(T2) cv (where cv represents the member function's cv-qualifiers); the type T1 is cv T0*
  • a class type with member types first_argument_type and second_argument_type; the type T1 is F::first_argument_type and the type T2 is F::second_argument_type.

Acknowledgements

Anna Salwa originally proposed not_fn in discussion group ISO C++ Standard - Future Proposals.

Mateusz Kwiatkowski, Jonathan Wakely and Daniel Krügler offered many useful suggestions and corrections to the proposal.

References

  1. Stephan T. Lavavej, "Making Operator Functors greater<>" (N3421, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3421.htm)
  2. Faisal Vali, Herb Sutter, Dave Abrahams, "Generic (Polymorphic) Lambda Expressions (Revision 3) " (N3649, http://isocpp.org/files/papers/N3649.html)