On Fri, 14 Nov 2025 at 06:51, Tomasz Kamiński <[email protected]> wrote:
>
> This patch changes the implementation of bind_front<f> and bind_back<f> to
> return a _Bind_front_t<_Bind_fn_t<f>, ...> and _Bind_back_t<_Bind_fn_t<f>, 
> ...>
> respectively, replacing the previous lambda-based implementation. The prior 
> use
> of a lambda caused non-conforming behavior with respect to C++23 
> [func.require]
> p8, which requires that bind_front<f>(s), bind_front<f>(move(s)), and
> bind_front<f>(as_const(s)) produce the same type.
>
> Additionally, using specialized structs reduces the size of the resulting 
> functor
> in certain scenarios (see PR).
>
> For the zero-argument case, the function still returns a _Bind_fn_t<f>. Since 
> this
> type is already a perfect forwarding call wrapper, it yields the same result 
> as
> _Bind_front_t<_Bind_fn_t<f>>.
>
> A consequence of this change is that the types returned by 
> bind_front<f>(args...)
> and bind_back<f>(args...) are no longer structural - they are not required to 
> be
> structural by the standard.
>
>         PR libstdc++/122032
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/functional (std::bind_front<f>, std::bind_back<f>):
>         Define in terms of _Bind_front_t/_Bind_back_t.
>         * testsuite/20_util/function_objects/bind_back/nttp.cc: New tests.
>         * testsuite/20_util/function_objects/bind_front/nttp.cc: New tests.
>
> Reviewed-by: Patrick Palka <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> v2 updates commit messages, per review suggestions.
>
> Tested on x86_64-linux. OK for trunk?

OK


>
>  libstdc++-v3/include/std/functional           | 50 +++----------------
>  .../function_objects/bind_back/nttp.cc        | 21 +++++++-
>  .../function_objects/bind_front/nttp.cc       | 21 +++++++-
>  3 files changed, 45 insertions(+), 47 deletions(-)
>
> diff --git a/libstdc++-v3/include/std/functional 
> b/libstdc++-v3/include/std/functional
> index dd1aa204eae..1928a27d3fd 100644
> --- a/libstdc++-v3/include/std/functional
> +++ b/libstdc++-v3/include/std/functional
> @@ -957,7 +957,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      }
>
>  #if __cpp_lib_bind_front >= 202306L
> -
>    /** Create call wrapper by partial application of arguments to function.
>     *
>     * The result of `std::bind_front<fn>(bind_args...)` is a function object
> @@ -970,32 +969,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    template<auto __fn, typename... _BindArgs>
>      constexpr decltype(auto)
>      bind_front(_BindArgs&&... __bind_args)
> -      noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> +    noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
>      {
>        using _Fn = decltype(__fn);
> -      static_assert(
> -       (is_constructible_v<decay_t<_BindArgs>, _BindArgs> && ...) &&
> -       (is_move_constructible_v<decay_t<_BindArgs>> && ...));
>        if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
>         static_assert(__fn != nullptr);
>
>        if constexpr (sizeof...(_BindArgs) == 0)
>         return _Bind_fn_t<__fn>{};
> -      else {
> -       return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
> -         <typename _Self, typename... _CallArgs>
> -         (this _Self&&, _CallArgs&&... __call_args)
> -           noexcept(is_nothrow_invocable_v<
> -             const _Fn&, __like_t<_Self, decay_t<_BindArgs>>..., 
> _CallArgs...>)
> -           -> decltype(auto)
> -           requires is_invocable_v<
> -             const _Fn&, __like_t<_Self, decay_t<_BindArgs>>..., 
> _CallArgs...>
> -         {
> -           return std::invoke(__fn,
> -             std::forward_like<_Self>(__bound_args)...,
> -             std::forward<_CallArgs>(__call_args)...);
> -         };
> -      }
> +      else
> +        return _Bind_front_t<_Bind_fn_t<__fn>, _BindArgs...>(0,
> +                _Bind_fn_t<__fn>{}, std::forward<_BindArgs>(__bind_args)...);
>      }
>
>  #endif // __cpp_lib_bind_front  // C++26
> @@ -1035,36 +1019,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    template<auto __fn, typename... _BindArgs>
>      constexpr decltype(auto)
>      bind_back(_BindArgs&&... __bind_args)
> -      noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
> +    noexcept(__and_v<is_nothrow_constructible<_BindArgs>...>)
>      {
>        using _Fn = decltype(__fn);
> -      static_assert(
> -       (is_constructible_v<decay_t<_BindArgs>, _BindArgs> && ...) &&
> -       (is_move_constructible_v<decay_t<_BindArgs>> && ...));
>        if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
>         static_assert(__fn != nullptr);
>
>        if constexpr (sizeof...(_BindArgs) == 0)
>         return _Bind_fn_t<__fn>{};
>        else
> -      {
> -       // Capture arguments in a lambda and return that.
> -       return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
> -         <typename _Self, typename... _CallArgs>
> -         (this _Self&&, _CallArgs&&... __call_args)
> -           noexcept(is_nothrow_invocable_v<
> -             const _Fn&, _CallArgs..., __like_t<_Self, 
> decay_t<_BindArgs>>...>)
> -           -> decltype(auto)
> -           requires is_invocable_v<
> -             const _Fn&, _CallArgs..., __like_t<_Self, 
> decay_t<_BindArgs>>...>
> -         {
> -           return std::invoke(__fn,
> -             std::forward<_CallArgs>(__call_args)...,
> -             std::forward_like<_Self>(__bound_args)...);
> -         };
> -      }
> +        return _Bind_back_t<_Bind_fn_t<__fn>, _BindArgs...>(0,
> +                _Bind_fn_t<__fn>{}, std::forward<_BindArgs>(__bind_args)...);
>      }
> -
>  #endif // __cpp_lib_bind_back  // C++26, nttp
>  #endif // __cpp_lib_bind_back
>
> @@ -1168,7 +1134,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      }
>
>  #if __cpp_lib_not_fn >= 202306L
> -
>    /** Wrap a function type to create a function object that negates its 
> result.
>     *
>     * The function template `std::not_fn` creates a "forwarding call wrapper",
> @@ -1196,7 +1161,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>             !std::invoke(__fn, std::forward<_Args>(__args)...); }
>         { return !std::invoke(__fn, std::forward<_Args>(__args)...); };
>      };
> -
>  #endif // __cpp_lib_not_fn >= 202306L
>  #endif // __cpp_lib_not_fn
>
> diff --git 
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc 
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> index 3bea8eced43..24bf0466e2d 100644
> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
> @@ -24,6 +24,25 @@ test01()
>    struct F { void operator()(int) {} };
>    constexpr F f{};
>
> +  // Arguments should be decayed:
> +  static_assert(std::is_same_v<
> +      decltype(bind_back<f>(std::declval<int>())),
> +      decltype(bind_back<f>(std::declval<int&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_back<f>(std::declval<int>())),
> +      decltype(bind_back<f>(std::declval<const int&>()))
> +      >);
> +
> +  static_assert(std::is_same_v<
> +      decltype(bind_back<f>(std::declval<int>(), std::declval<float>())),
> +      decltype(bind_back<f>(std::declval<int&>(), std::declval<float&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_back<f>(std::declval<int>(), std::declval<float>())),
> +      decltype(bind_back<f>(std::declval<const int&>(), std::declval<const 
> float&>()))
> +      >);
> +
>    // Reference wrappers should be handled:
>    static_assert(!std::is_same_v<
>        decltype(bind_back<f>(std::declval<int&>())),
> @@ -270,10 +289,8 @@ test04()
>    VERIFY(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 );
>    constexpr auto g2 = bind_back<f>(1, 2);
>    VERIFY(g2(3) == 2*1 + 3*2 + 1*3 );
> -  VERIFY(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 );
>    constexpr auto g3 = bind_back<f>(1, 2, 3);
>    VERIFY(g3() == 1 + 2*2 + 3*3);
> -  VERIFY(bind_back<g2>(3)() == 1*2 + 2*3 + 3*1 );
>    return true;
>  }
>
> diff --git 
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc 
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> index 9eb3c432a86..cf84353887b 100644
> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
> @@ -24,6 +24,25 @@ test01()
>    struct F { void operator()(int) {} };
>    constexpr F f{};
>
> +  // Arguments should be decayed:
> +  static_assert(std::is_same_v<
> +      decltype(bind_front<f>(std::declval<int>())),
> +      decltype(bind_front<f>(std::declval<int&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_front<f>(std::declval<int>())),
> +      decltype(bind_front<f>(std::declval<const int&>()))
> +      >);
> +
> +  static_assert(std::is_same_v<
> +      decltype(bind_front<f>(std::declval<int>(), std::declval<float>())),
> +      decltype(bind_front<f>(std::declval<int&>(), std::declval<float&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_front<f>(std::declval<int>(), std::declval<float>())),
> +      decltype(bind_front<f>(std::declval<const int&>(), std::declval<const 
> float&>()))
> +      >);
> +
>    // Reference wrappers should be handled:
>    static_assert(!std::is_same_v<
>        decltype(bind_front<f>(std::declval<int&>())),
> @@ -269,10 +288,8 @@ test04()
>    VERIFY( bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 );
>    constexpr auto g2 = bind_front<f>(1, 2);
>    VERIFY( g2(3) == 1 + 2*2 + 3*3 );
> -  VERIFY( bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 );
>    constexpr auto g3 = bind_front<f>(1, 2, 3);
>    VERIFY( g3() == 1 + 2*2 + 3*3 );
> -  VERIFY(bind_front<g2>(3)() == 1 + 2*2 + 3*3 );
>    return true;
>  }
>
> --
> 2.51.0
>

Reply via email to