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

Reply via email to