On Fri, Jul 4, 2025 at 6:39 AM Nathan Myers <n...@cantrip.org> wrote:
> This is a snapshot of work on P2714 "Bind front and back to NTTP > callables", posted for reference. > > Questions: > 1. Jonathan asks if __type_forward_like_t does the same job as __like_t > in bits/move.h. > 2. Could the "if constexpr" statements be better expressed as requires > clauses via the A=>B == !A||B identity? > I would create a wrappers for no bound arguments case, as bind_front/bind_back have exactly the same behavior here. We may also want to consider a separate type for single argument case, that correspond to function_ref nontype, ptr/ref constructor. > > libstdc++-v3/ChangeLog: > PR libstdc++/119744 > * include/bits/version.def: Redefine __cpp_lib_bind_front etc. > * include/bits/version.h: Ditto. > * include/std/functional: Add new bind_front etc. overloads > --- > libstdc++-v3/include/bits/version.def | 12 +++ > libstdc++-v3/include/bits/version.h | 21 ++++- > libstdc++-v3/include/std/functional | 124 +++++++++++++++++++++++++- > 3 files changed, 153 insertions(+), 4 deletions(-) > > diff --git a/libstdc++-v3/include/bits/version.def > b/libstdc++-v3/include/bits/version.def > index 5d5758bf203..8ab9a7207e7 100644 > --- a/libstdc++-v3/include/bits/version.def > +++ b/libstdc++-v3/include/bits/version.def > @@ -463,6 +463,10 @@ ftms = { > > ftms = { > name = not_fn; > + values = { > + v = 202306; > + cxxmin = 26; > + }; > values = { > v = 201603; > cxxmin = 17; > @@ -776,6 +780,10 @@ ftms = { > > ftms = { > name = bind_front; > + values = { > + v = 202306; > + cxxmin = 26; > + }; > values = { > v = 201907; > cxxmin = 20; > @@ -784,6 +792,10 @@ ftms = { > > ftms = { > name = bind_back; > + values = { > + v = 202306; > + cxxmin = 26; > + }; > values = { > v = 202202; > cxxmin = 23; > diff --git a/libstdc++-v3/include/bits/version.h > b/libstdc++-v3/include/bits/version.h > index 2b00e8419b3..c204ae3c48c 100644 > --- a/libstdc++-v3/include/bits/version.h > +++ b/libstdc++-v3/include/bits/version.h > @@ -511,7 +511,12 @@ > #undef __glibcxx_want_make_from_tuple > > #if !defined(__cpp_lib_not_fn) > -# if (__cplusplus >= 201703L) > +# if (__cplusplus > 202302L) > +# define __glibcxx_not_fn 202306L > +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) > +# define __cpp_lib_not_fn 202306L > +# endif > +# elif (__cplusplus >= 201703L) > # define __glibcxx_not_fn 201603L > # if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) > # define __cpp_lib_not_fn 201603L > @@ -866,7 +871,12 @@ > #undef __glibcxx_want_atomic_value_initialization > > #if !defined(__cpp_lib_bind_front) > -# if (__cplusplus >= 202002L) > +# if (__cplusplus > 202302L) > +# define __glibcxx_bind_front 202306L > +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) > +# define __cpp_lib_bind_front 202306L > +# endif > +# elif (__cplusplus >= 202002L) > # define __glibcxx_bind_front 201907L > # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) > # define __cpp_lib_bind_front 201907L > @@ -876,7 +886,12 @@ > #undef __glibcxx_want_bind_front > > #if !defined(__cpp_lib_bind_back) > -# if (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) > +# if (__cplusplus > 202302L) > +# define __glibcxx_bind_back 202306L > +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) > +# define __cpp_lib_bind_back 202306L > +# endif > +# elif (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) > # define __glibcxx_bind_back 202202L > # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) > # define __cpp_lib_bind_back 202202L > diff --git a/libstdc++-v3/include/std/functional > b/libstdc++-v3/include/std/functional > index 307bcb95bcc..21f0b1cb2d5 100644 > --- a/libstdc++-v3/include/std/functional > +++ b/libstdc++-v3/include/std/functional > @@ -940,7 +940,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _M_bound_args(std::forward<_Args>(__args)...) > { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); } > > -#if __cpp_explicit_this_parameter > +#ifdef __cpp_explicit_this_parameter > template<typename _Self, typename... _CallArgs> > constexpr > invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self, > _BoundArgs>..., _CallArgs...> > @@ -1218,8 +1218,130 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { > return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; > } > +#if __cpp_lib_not_fn >= 202306 > + /** Wrap a function type to create a function object that negates its > result. > + * > + * The function template `std::not_fn` creates a "forwarding call > wrapper", > + * which is a function object that when called forwards its arguments to > + * its invocable template argument. > + * > + * The result of invoking the wrapper is the negation (using `!`) of > + * the wrapped function object. > + * > + * @ingroup functors > + * @since C++26 > + */ > + template<auto __fn> > + constexpr auto > + not_fn() noexcept > + { > + using _Fn = decltype(__fn); > + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { > + static_assert(__fn != nullptr); > + } > + return []<typename... _Tp> (_Tp&&... __call_args) > Make the call operator static. > + noexcept(is_nothrow_invocable_v<_Fn, _Tp...>) > Noexcept does not take the operator!() that may be overloaded, and throws. > + -> invoke_result_t<_Fn, _Tp...> > Similarly, this should be decltype(!invoke()), the __fn may return something that has operator!, or is contextualy convertible to bool, like range or pointer. + { > + return !invoke(__fn, forward<_Tp>(__call_args)...); > + }; > + } > +#endif // __cpp_lib_not_fn >= 202306 > +#endif // __cpp_lib_not_fn > + > +#if __cpp_lib_bind_front >= 202306 || __cpp_lib_bind_back >= 202306 > + > + template<typename _Tp, typename _Up> > + using __copy_const = conditional<is_const_v<_Tp>, const _Up, _Up>; > + > + template<typename _Tp, typename _Up, > + typename _Xp = __copy_const<remove_reference_t<_Tp>, _Up>::type> > + using __copy_value_category = > + conditional<is_lvalue_reference_v<_Tp&&>, _Xp&, _Xp&&>; > + > + template<typename _Tp, typename _Up> > + using __type_forward_like = > + __copy_value_category<_Tp, remove_reference_t<_Up>>; > + > + template<typename _Tp, typename _Up> > + using __type_forward_like_t = __type_forward_like<_Tp, _Up>::type; > We have forward like in bits/move.h, that is c++23 plus, so I would copy it. > + > #endif > > +#if __cpp_lib_bind_front >= 202306 > + /** Create call wrapper by partial application of arguments to function. > + * > + * The result of `std::bind_front<f>(args...)` is a function object that > + * stores the bound arguments, `args...`. When that function object is > + * invoked with `call_args...` it returns the result of calling > + * `f(args..., call_args...)`. > + * > + * @since C++26 > + */ > + > + template<auto __fn, typename... _Args> > + constexpr auto > + bind_front(_Args&&... __args) > + requires ( > + (is_constructible_v<decay_t<_Args>, _Args> and ...) and > + (is_move_constructible_v<decay_t<_Args>> and ...)) > + { > + using _Fn = decltype(__fn); > + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { > + static_assert(__fn != nullptr); > + } > + return [... __bound_args(std::forward<_Args>(__args))]< > + typename _Self, typename... _Tp> > + (this _Self&&, _Tp&&... __call_args) > + noexcept(is_nothrow_invocable_v< > + _Fn, __type_forward_like_t<_Self, decay_t<_Args>>..., _Tp...>) > + -> invoke_result_t< > + _Fn, __type_forward_like_t<_Self, decay_t<_Args>>..., _Tp...> > + { > + return invoke(__fn, > + forward_like<_Self>(__bound_args)..., > + forward<_Tp>(__call_args)...); > + }; > + } > +#endif // __cpp_lib_bind_front > + > +#if __cpp_lib_bind_back >= 202306 > + /** Create call wrapper by partial application of arguments to function. > + * > + * The result of `std::bind_back<f>(args...)` is a function object that > + * stores the bound arguments, `args...`. When that function object is > + * invoked with `call_args...` it returns the result of calling > + * `f(call_args..., args...)`. > + * > + * @since C++26 > + */ > + > + template<auto __fn, typename... _Args> > + constexpr auto > + bind_back(_Args&&... __args) > + requires ( > + (is_constructible_v<decay_t<_Args>, _Args> and ...) and > + (is_move_constructible_v<decay_t<_Args>> and ...)) > + { > + using _Fn = decltype(__fn); > + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { > + static_assert(__fn != nullptr); > + } > I do not think the wording allows us to add this precondition, it is fine to bind a null-pointer, unless you call it. > + return [... __bound_args(std::forward<_Args>(__args))]< > + typename _Self, typename... _Tp> > + (this _Self&&, _Tp&&... __call_args) > + noexcept(is_nothrow_invocable_v< > + _Fn, _Tp..., __type_forward_like_t<_Self, decay_t<_Args>>...>) > + -> invoke_result_t< > + _Fn, _Tp..., __type_forward_like_t<_Self, decay_t<_Args>>...> > + { > + return invoke(__fn, > + forward<_Tp>(__call_args)..., > + forward_like<_Self>(__bound_args)...); > + }; > + } > +#endif // __cpp_lib_bind_back > + > #if __cplusplus >= 201703L > // Searchers > > -- > 2.50.0 > >