[PATCH v2] libstdc++: Refactor bound arguments storage for bind_front/back
Tomasz Kaminski
tkaminsk@redhat.com
Tue Aug 26 16:01:40 GMT 2025
On Tue, Aug 26, 2025 at 5:50 PM Patrick Palka <ppalka@redhat.com> wrote:
> On Tue, 26 Aug 2025, Jonathan Wakely wrote:
>
> > On 20/08/25 10:13 +0200, Tomasz Kamiński wrote:
> > > This patch refactors the implementation of bind_front and bind_back to
> > > avoid using std::tuple for argument storage. Instead, bound arguments
> are
> > > now:
> > > * stored directly if there is only one,
> > > * within a dedicated _Bound_arg_storage otehrwise.
> >
> > "otehrwise" -> "otherwise"
> >
> > As Patrick requested, I think it would be useful to mention (briefly)
> > that _Bound_arg_storage is cheaper to instantiate and access than
> > std::tuple, and that it can be trivially copyable because it doesn't
> > need a non-trivial assignment operator to deal with references types.
> >
> > OK for trunk with those changes.
> >
> > I agree with adding _Partial to the list of types to audit before
> > calling C++20 stable.
>
> I just remembered that _Partial already has a partial specialization
> for the single-bound-arg case that is optimal. And the vast majority of
> current standard range adaptors (or perhaps all) take at most one bound
> arg so we wouldn't see much real-world benefit optimizing the _Partial
> primary template, but I suppose we should still do it.
>
I wonder if we should make _Bind_back_t available in C++20 (without the
bind_back
funciton), and just derive _Patial from it. And remove partial
specializations.
We may need to do dance around explicit this, but I think it may be
worthwhile exploring.
>
> >
> > > _Bound_arg_storage holds each argument in an _Indexed_bound_arg base
> object.
> > > The base class is parameterized by both type and index to allow storing
> > > multiple arguments of the same type. Invocations are handled by
> > > _S_apply_front
> > > amd _S_apply_back static functions, which simulate explicit object
> > > parameters.
> > > To facilitate this, the __like_t alias template is now unconditionally
> > > available
> > > since C++11 in bits/move.h.
> > >
> > > libstdc++-v3/ChangeLog:
> > >
> > > * include/bits/move.h (std::__like_impl, std::__like_t): Make
> > > available in c++11.
> > > * include/std/functional (std::_Indexed_bound_arg)
> > > (std::_Bound_arg_storage, std::__make_bound_args): Define.
> > > (std::_Bind_front, std::_Bind_back): Use _Bound_arg_storage.
> > > * testsuite/20_util/function_objects/bind_back/1.cc: Expand
> > > test to cover cases of 0, 1, many bound args.
> > > * testsuite/20_util/function_objects/bind_back/111327.cc: Likewise.
> > > * testsuite/20_util/function_objects/bind_front/1.cc: Likewise.
> > > * testsuite/20_util/function_objects/bind_front/111327.cc:
> Likewise.
> > >
> > > Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> > > ---
> > > Changes in v2:
> > > * replace few 8 spaces with tabs
> > > * add more test cases, covering materialization
> > >
> > > Testing on x86_64-linux, all bind test passed.
> > > OK for trunk when test passes.
> > >
> > > libstdc++-v3/include/bits/move.h | 2 +-
> > > libstdc++-v3/include/std/functional | 113 +++++++++---
> > > .../20_util/function_objects/bind_back/1.cc | 166 ++++++++++++++++--
> > > .../function_objects/bind_back/111327.cc | 11 ++
> > > .../20_util/function_objects/bind_front/1.cc | 164 +++++++++++++++--
> > > .../function_objects/bind_front/111327.cc | 11 ++
> > > 6 files changed, 418 insertions(+), 49 deletions(-)
> > >
> > > diff --git a/libstdc++-v3/include/bits/move.h
> > > b/libstdc++-v3/include/bits/move.h
> > > index 061e6b4de3d..8c4f461a110 100644
> > > --- a/libstdc++-v3/include/bits/move.h
> > > +++ b/libstdc++-v3/include/bits/move.h
> > > @@ -89,7 +89,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > return static_cast<_Tp&&>(__t);
> > > }
> > >
> > > -#if __glibcxx_forward_like // C++ >= 23
> > > template<typename _Tp, typename _Up>
> > > struct __like_impl; // _Tp must be a reference and _Up an lvalue
> reference
> > >
> > > @@ -112,6 +111,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > template<typename _Tp, typename _Up>
> > > using __like_t = typename __like_impl<_Tp&&, _Up&>::type;
> > >
> > > +#if __glibcxx_forward_like // C++ >= 23
> > > /** @brief Forward with the cv-qualifiers and value category of
> another
> > > type.
> > > * @tparam _Tp An lvalue reference or rvalue reference.
> > > * @tparam _Up An lvalue reference type deduced from the function
> > > argument.
> > > diff --git a/libstdc++-v3/include/std/functional
> > > b/libstdc++-v3/include/std/functional
> > > index 307bcb95bcc..b1cda87929d 100644
> > > --- a/libstdc++-v3/include/std/functional
> > > +++ b/libstdc++-v3/include/std/functional
> > > @@ -922,6 +922,53 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > }
> > >
> > > #ifdef __cpp_lib_bind_front // C++ >= 20
> > > + template<size_t, typename _Tp>
> > > + struct _Indexed_bound_arg
> > > + {
> > > + [[no_unique_address]] _Tp _M_val;
> > > + };
> > > +
> > > + template<typename... _IndexedArgs>
> > > + struct _Bound_arg_storage : _IndexedArgs...
> > > + {
> > > + template<typename _Fd, typename _Self, typename... _CallArgs>
> > > + static constexpr
> > > + decltype(auto)
> > > + _S_apply_front(_Fd&& __fd, _Self&& __self, _CallArgs&&...
> __call_args)
> > > + {
> > > + return std::invoke(std::forward<_Fd>(__fd),
> > > + __like_t<_Self,
> _IndexedArgs>(__self)._M_val...,
> > > + std::forward<_CallArgs>(__call_args)...);
> > > + }
> > > +
> > > + template<typename _Fd, typename _Self, typename... _CallArgs>
> > > + static constexpr
> > > + decltype(auto)
> > > + _S_apply_back(_Fd&& __fd, _Self&& __self, _CallArgs&&...
> __call_args)
> > > + {
> > > + return std::invoke(std::forward<_Fd>(__fd),
> > > + std::forward<_CallArgs>(__call_args)...,
> > > + __like_t<_Self,
> _IndexedArgs>(__self)._M_val...);
> > > + }
> > > + };
> > > +
> > > + template<typename... _BoundArgs, typename... _Args>
> > > + constexpr auto
> > > + __make_bound_args(_Args&&... __args)
> > > + {
> > > + if constexpr (sizeof...(_BoundArgs) == 1)
> > > + // pack has one element, so return copy of arg
> > > + return (_BoundArgs(std::forward<_Args>(__args)), ...);
> > > + else
> > > + {
> > > + auto __impl = [&]<size_t... _Inds>(index_sequence<_Inds...>)
> > > + {
> > > + return _Bound_arg_storage<_Indexed_bound_arg<_Inds,
> > > _BoundArgs>...>
> > > + { {_BoundArgs(std::forward<_Args>(__args))}... };
> > > + };
> > > + return __impl(index_sequence_for<_BoundArgs...>());
> > > + }
> > > + }
> > >
> > > template<typename _Fd, typename... _BoundArgs>
> > > struct _Bind_front
> > > @@ -937,7 +984,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>,
> > > is_nothrow_constructible<_BoundArgs,
> > > _Args>...>::value)
> > > : _M_fd(std::forward<_Fn>(__fn)),
> > > - _M_bound_args(std::forward<_Args>(__args)...)
> > > +
> > >
> _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...))
> > > { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); }
> > >
> > > #if __cpp_explicit_this_parameter
> > > @@ -948,7 +995,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>,
> > > __like_t<_Self, _BoundArgs>...,
> > > _CallArgs...>)
> > > {
> > > - return _S_call(__like_t<_Self, _Bind_front>(__self),
> > > _BoundIndices(),
> > > + return _S_call(__like_t<_Self, _Bind_front>(__self),
> > > std::forward<_CallArgs>(__call_args)...);
> > > }
> > > #else
> > > @@ -959,8 +1006,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > operator()(_CallArgs&&... __call_args) &
> > > noexcept(is_nothrow_invocable_v<_Fd&, _BoundArgs&...,
> _CallArgs...>)
> > > {
> > > - return _S_call(*this, _BoundIndices(),
> > > - std::forward<_CallArgs>(__call_args)...);
> > > + return _S_call(*this, std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > template<typename... _CallArgs>
> > > @@ -971,8 +1017,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(is_nothrow_invocable_v<const _Fd&, const _BoundArgs&...,
> > > _CallArgs...>)
> > > {
> > > - return _S_call(*this, _BoundIndices(),
> > > - std::forward<_CallArgs>(__call_args)...);
> > > + return _S_call(*this, std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > template<typename... _CallArgs>
> > > @@ -982,8 +1027,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > operator()(_CallArgs&&... __call_args) &&
> > > noexcept(is_nothrow_invocable_v<_Fd, _BoundArgs..., _CallArgs...>)
> > > {
> > > - return _S_call(std::move(*this), _BoundIndices(),
> > > - std::forward<_CallArgs>(__call_args)...);
> > > + return _S_call(std::move(*this),
> > > + std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > template<typename... _CallArgs>
> > > @@ -994,8 +1039,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(is_nothrow_invocable_v<const _Fd, const _BoundArgs...,
> > > _CallArgs...>)
> > > {
> > > - return _S_call(std::move(*this), _BoundIndices(),
> > > - std::forward<_CallArgs>(__call_args)...);
> > > + return _S_call(std::move(*this),
> > > + std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > template<typename... _CallArgs>
> > > @@ -1012,20 +1057,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > #endif
> > >
> > > private:
> > > - using _BoundIndices = index_sequence_for<_BoundArgs...>;
> > > + using _BoundArgsStorage
> > > + // _BoundArgs are required to be move-constructible, so this is
> valid.
> > > + =
> > >
> decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...));
> > >
> > > - template<typename _Tp, size_t... _Ind, typename... _CallArgs>
> > > + template<typename _Tp, typename... _CallArgs>
> > > static constexpr
> > > decltype(auto)
> > > - _S_call(_Tp&& __g, index_sequence<_Ind...>, _CallArgs&&...
> > > __call_args)
> > > + _S_call(_Tp&& __g, _CallArgs&&... __call_args)
> > > {
> > > - return std::invoke(std::forward<_Tp>(__g)._M_fd,
> > > - std::get<_Ind>(std::forward<_Tp>(__g)._M_bound_args)...,
> > > - std::forward<_CallArgs>(__call_args)...);
> > > + if constexpr (sizeof...(_BoundArgs) == 1)
> > > + return std::invoke(std::forward<_Tp>(__g)._M_fd,
> > > + std::forward<_Tp>(__g)._M_bound_args,
> > > + std::forward<_CallArgs>(__call_args)...);
> > > + else
> > > + return _BoundArgsStorage::_S_apply_front(
> > > + std::forward<_Tp>(__g)._M_fd,
> > > + std::forward<_Tp>(__g)._M_bound_args,
> > > + std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > [[no_unique_address]] _Fd _M_fd;
> > > - [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args;
> > > + [[no_unique_address]] _BoundArgsStorage _M_bound_args;
> > > };
> > >
> > > template<typename _Fn, typename... _Args>
> > > @@ -1066,7 +1119,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>,
> > > is_nothrow_constructible<_BoundArgs,
> > > _Args>...>::value)
> > > : _M_fd(std::forward<_Fn>(__fn)),
> > > - _M_bound_args(std::forward<_Args>(__args)...)
> > > +
> > >
> _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...))
> > > { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); }
> > >
> > > template<typename _Self, typename... _CallArgs>
> > > @@ -1076,25 +1129,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>,
> > > _CallArgs..., __like_t<_Self,
> > > _BoundArgs>...>)
> > > {
> > > - return _S_call(__like_t<_Self, _Bind_back>(__self),
> _BoundIndices(),
> > > + return _S_call(__like_t<_Self, _Bind_back>(__self),
> > > std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > private:
> > > - using _BoundIndices = index_sequence_for<_BoundArgs...>;
> > > + using _BoundArgsStorage
> > > + // _BoundArgs are required to be move-constructible, so this is
> valid.
> > > + =
> > >
> decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...));
> > >
> > > - template<typename _Tp, size_t... _Ind, typename... _CallArgs>
> > > + template<typename _Tp, typename... _CallArgs>
> > > static constexpr
> > > decltype(auto)
> > > - _S_call(_Tp&& __g, index_sequence<_Ind...>, _CallArgs&&...
> > > __call_args)
> > > + _S_call(_Tp&& __g, _CallArgs&&... __call_args)
> > > {
> > > - return std::invoke(std::forward<_Tp>(__g)._M_fd,
> > > - std::forward<_CallArgs>(__call_args)...,
> > > - std::get<_Ind>(std::forward<_Tp>(__g)._M_bound_args)...);
> > > + if constexpr (sizeof...(_BoundArgs) == 1)
> > > + return std::invoke(std::forward<_Tp>(__g)._M_fd,
> > > + std::forward<_CallArgs>(__call_args)...,
> > > + std::forward<_Tp>(__g)._M_bound_args);
> > > + else
> > > + return _BoundArgsStorage::_S_apply_back(
> > > + std::forward<_Tp>(__g)._M_fd,
> > > + std::forward<_Tp>(__g)._M_bound_args,
> > > + std::forward<_CallArgs>(__call_args)...);
> > > }
> > >
> > > [[no_unique_address]] _Fd _M_fd;
> > > - [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args;
> > > + [[no_unique_address]] _BoundArgsStorage _M_bound_args;
> > > };
> > >
> > > template<typename _Fn, typename... _Args>
> > > diff --git
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> > > b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> > > index c31d3228815..a31528fc755 100644
> > > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> > > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> > > @@ -48,6 +48,16 @@ test01()
> > > decltype(bind_back(std::declval<const F&>(), std::declval<const
> > > int&>()))
> > > >);
> > >
> > > + static_assert(std::is_same_v<
> > > + decltype(bind_back(std::declval<F>(), std::declval<int>(),
> > > std::declval<float>())),
> > > + decltype(bind_back(std::declval<F&>(), std::declval<int&>(),
> > > std::declval<float&>()))
> > > + >);
> > > + static_assert(std::is_same_v<
> > > + decltype(bind_back(std::declval<F>(), std::declval<int>(),
> > > std::declval<float>())),
> > > + decltype(bind_back(std::declval<const F&>(), std::declval<const
> > > int&>(), std::declval<const float&>()))
> > > + >);
> > > +
> > > +
> > > // Reference wrappers should be handled:
> > > static_assert(!std::is_same_v<
> > > decltype(bind_back(std::declval<F>(), std::declval<int&>())),
> > > @@ -63,29 +73,58 @@ test01()
> > > >);
> > > }
> > >
> > > +struct quals
> > > +{
> > > + bool as_const;
> > > + bool as_lvalue;
> > > +};
> > > +
> > > +template<typename... Args>
> > > void
> > > -test02()
> > > +testTarget(Args... args)
> > > {
> > > - struct quals
> > > + struct F
> > > {
> > > - bool as_const;
> > > - bool as_lvalue;
> > > + quals operator()(Args...) & { return { false, true }; }
> > > + quals operator()(Args...) const & { return { true, true }; }
> > > + quals operator()(Args...) && { return { false, false }; }
> > > + quals operator()(Args...) const && { return { true, false }; }
> > > };
> > >
> > > + F f;
> > > + auto g = bind_back(f, args...);
> > > + const auto& cg = g;
> > > + quals q;
> > > +
> > > + // constness and value category should be forwarded to the target
> object:
> > > + q = g();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(g)();
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = cg();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(cg)();
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > +}
> > > +
> > > +template<typename... Args>
> > > +void
> > > +testBoundArgs(Args... args)
> > > +{
> > > struct F
> > > {
> > > - quals operator()() & { return { false, true }; }
> > > - quals operator()() const & { return { true, true }; }
> > > - quals operator()() && { return { false, false }; }
> > > - quals operator()() const && { return { true, false }; }
> > > + quals operator()(Args..., int&) const { return { false, true }; }
> > > + quals operator()(Args..., int const&) const { return { true, true
> }; }
> > > + quals operator()(Args..., int&&) const { return { false, false };
> }
> > > + quals operator()(Args..., int const&&) const { return { true,
> false };
> > > }
> > > };
> > >
> > > F f;
> > > - auto g = bind_back(f);
> > > + auto g = bind_back(f, args..., 10);
> > > const auto& cg = g;
> > > quals q;
> > >
> > > - // constness and value category should be forwarded to the target
> object:
> > > + // constness and value category should be forwarded to the bound
> objects:
> > > q = g();
> > > VERIFY( ! q.as_const && q.as_lvalue );
> > > q = std::move(g)();
> > > @@ -94,6 +133,70 @@ test02()
> > > VERIFY( q.as_const && q.as_lvalue );
> > > q = std::move(cg)();
> > > VERIFY( q.as_const && ! q.as_lvalue );
> > > +
> > > + int i = 0;
> > > + auto gr = bind_back(f, args..., std::ref(i));
> > > + const auto& cgr = gr;
> > > +
> > > + // bound object is reference wrapper
> > > + q = gr();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(gr)();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = cgr();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(cgr)();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > +
> > > + auto gcr = bind_back(f, args..., std::cref(i));
> > > + const auto& cgcr = gcr;
> > > +
> > > + q = gcr();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(gcr)();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = cgcr();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(cgcr)();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > +}
> > > +
> > > +template<typename... Args>
> > > +void
> > > +testCallArgs(Args... args)
> > > +{
> > > + struct F
> > > + {
> > > + quals operator()(int&, Args...) const { return { false, true }; }
> > > + quals operator()(int const&, Args...) const { return { true, true
> }; }
> > > + quals operator()(int&&, Args...) const { return { false, false };
> }
> > > + quals operator()(int const&&, Args...) const { return { true,
> false };
> > > }
> > > + };
> > > +
> > > + F f;
> > > + auto g = bind_back(f, args...);
> > > + const auto& cg = g;
> > > + quals q;
> > > + int i = 10;
> > > + const int ci = i;
> > > +
> > > + q = g(i);
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = g(std::move(i));
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = g(ci);
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = g(std::move(ci));
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > +
> > > + q = cg(i);
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = cg(std::move(i));
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = cg(ci);
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = cg(std::move(ci));
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > }
> > >
> > > void
> > > @@ -168,11 +271,52 @@ test04()
> > > return true;
> > > }
> > >
> > > +struct CountedArg
> > > +{
> > > + CountedArg() = default;
> > > + CountedArg(CountedArg&& f) noexcept : counter(f.counter) {
> ++counter; }
> > > + CountedArg& operator=(CountedArg&&) = delete;
> > > +
> > > + int counter = 0;
> > > +};
> > > +CountedArg const c;
> > > +
> > > +void
> > > +testMaterialization()
> > > +{
> > > + struct F
> > > + {
> > > + int operator()(CountedArg arg, int) const
> > > + { return arg.counter; };
> > > + };
> > > +
> > > + // CountedArg is bound to rvalue-reference thus moved
> > > + auto f0 = std::bind_back(F{});
> > > + VERIFY( f0(CountedArg(), 10) == 1 );
> > > +
> > > + auto f1 = std::bind_back(F{}, 10);
> > > + VERIFY( f1(CountedArg()) == 1 );
> > > +}
> > > +
> > > int
> > > main()
> > > {
> > > test01();
> > > - test02();
> > > test03();
> > > +
> > > + testTarget();
> > > + testTarget(10);
> > > + testTarget(10, 20, 30);
> > > +
> > > + testBoundArgs();
> > > + testBoundArgs(10);
> > > + testBoundArgs(10, 20, 30);
> > > +
> > > + testCallArgs();
> > > + testCallArgs(10);
> > > + testCallArgs(10, 20, 30);
> > > +
> > > + testMaterialization();
> > > +
> > > static_assert(test04());
> > > }
> > > diff --git
> > > a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> > > b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> > > index d634db9dc1d..de3ae47e37f 100644
> > > ---
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> > > +++
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> > > @@ -37,6 +37,17 @@ int main() {
> > > g1(); // { dg-error "deleted|no match" }
> > > std::move(g1)(); // { dg-error "deleted|no match" }
> > > std::move(std::as_const(g1))();
> > > +
> > > + auto f2 = std::bind_back(F{}, 42, 10);
> > > + f2(); // { dg-error "deleted|no match" }
> > > + std::move(f2)();
> > > + std::as_const(f2)();
> > > + std::move(std::as_const(f2))();
> > > +
> > > + auto g2 = std::bind_back(G{}, 42, 10);
> > > + g2(); // { dg-error "deleted|no match" }
> > > + std::move(g2)(); // { dg-error "deleted|no match" }
> > > + std::move(std::as_const(g2))();
> > > }
> > >
> > > // { dg-error "no type named 'type' in 'struct std::invoke_result" "" {
> > > target c++23 } 0 }
> > > diff --git
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
> > > b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
> > > index 57482c52263..ef28de8321b 100644
> > > --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
> > > +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
> > > @@ -48,6 +48,15 @@ test01()
> > > decltype(bind_front(std::declval<const F&>(), std::declval<const
> > > int&>()))
> > > >);
> > >
> > > + static_assert(std::is_same_v<
> > > + decltype(bind_front(std::declval<F>(), std::declval<int>(),
> > > std::declval<float>())),
> > > + decltype(bind_front(std::declval<F&>(), std::declval<int&>(),
> > > std::declval<float&>()))
> > > + >);
> > > + static_assert(std::is_same_v<
> > > + decltype(bind_front(std::declval<F>(), std::declval<int>(),
> > > std::declval<float>())),
> > > + decltype(bind_front(std::declval<const F&>(), std::declval<const
> > > int&>(), std::declval<const float&>()))
> > > + >);
> > > +
> > > // Reference wrappers should be handled:
> > > static_assert(!std::is_same_v<
> > > decltype(bind_front(std::declval<F>(), std::declval<int&>())),
> > > @@ -63,29 +72,58 @@ test01()
> > > >);
> > > }
> > >
> > > +struct quals
> > > +{
> > > + bool as_const;
> > > + bool as_lvalue;
> > > +};
> > > +
> > > +template<typename... Args>
> > > void
> > > -test02()
> > > +testTarget(Args... args)
> > > {
> > > - struct quals
> > > + struct F
> > > {
> > > - bool as_const;
> > > - bool as_lvalue;
> > > + quals operator()(Args...) & { return { false, true }; }
> > > + quals operator()(Args...) const & { return { true, true }; }
> > > + quals operator()(Args...) && { return { false, false }; }
> > > + quals operator()(Args...) const && { return { true, false }; }
> > > };
> > >
> > > + F f;
> > > + auto g = bind_front(f, args...);
> > > + const auto& cg = g;
> > > + quals q;
> > > +
> > > + // constness and value category should be forwarded to the target
> object:
> > > + q = g();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(g)();
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = cg();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(cg)();
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > +}
> > > +
> > > +template<typename... Args>
> > > +void
> > > +testBoundArgs(Args... args)
> > > +{
> > > struct F
> > > {
> > > - quals operator()() & { return { false, true }; }
> > > - quals operator()() const & { return { true, true }; }
> > > - quals operator()() && { return { false, false }; }
> > > - quals operator()() const && { return { true, false }; }
> > > + quals operator()(Args..., int&) const { return { false, true }; }
> > > + quals operator()(Args..., int const&) const { return { true, true
> }; }
> > > + quals operator()(Args..., int&&) const { return { false, false };
> }
> > > + quals operator()(Args..., int const&&) const { return { true,
> false };
> > > }
> > > };
> > >
> > > F f;
> > > - auto g = bind_front(f);
> > > + auto g = bind_front(f, args..., 10);
> > > const auto& cg = g;
> > > quals q;
> > >
> > > - // constness and value category should be forwarded to the target
> object:
> > > + // constness and value category should be forwarded to the bound
> objects:
> > > q = g();
> > > VERIFY( ! q.as_const && q.as_lvalue );
> > > q = std::move(g)();
> > > @@ -94,6 +132,70 @@ test02()
> > > VERIFY( q.as_const && q.as_lvalue );
> > > q = std::move(cg)();
> > > VERIFY( q.as_const && ! q.as_lvalue );
> > > +
> > > + int i = 0;
> > > + auto gr = bind_front(f, args..., std::ref(i));
> > > + const auto& cgr = gr;
> > > +
> > > + // bound object is reference wrapper, converts to same type of
> reference
> > > + q = gr();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(gr)();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = cgr();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = std::move(cgr)();
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > +
> > > + auto gcr = bind_front(f, args..., std::cref(i));
> > > + const auto& cgcr = gcr;
> > > +
> > > + q = gcr();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(gcr)();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = cgcr();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = std::move(cgcr)();
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > +}
> > > +
> > > +template<typename... Args>
> > > +void
> > > +testCallArgs(Args... args)
> > > +{
> > > + struct F
> > > + {
> > > + quals operator()(Args..., int&) const { return { false, true }; }
> > > + quals operator()(Args..., int const&) const { return { true, true
> }; }
> > > + quals operator()(Args..., int&&) const { return { false, false };
> }
> > > + quals operator()(Args..., int const&&) const { return { true,
> false };
> > > }
> > > + };
> > > +
> > > + F f;
> > > + auto g = bind_front(f, args...);
> > > + const auto& cg = g;
> > > + quals q;
> > > + int i = 10;
> > > + const int ci = i;
> > > +
> > > + q = g(i);
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = g(std::move(i));
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = g(ci);
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = g(std::move(ci));
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > +
> > > + q = cg(i);
> > > + VERIFY( ! q.as_const && q.as_lvalue );
> > > + q = cg(std::move(i));
> > > + VERIFY( ! q.as_const && ! q.as_lvalue );
> > > + q = cg(ci);
> > > + VERIFY( q.as_const && q.as_lvalue );
> > > + q = cg(std::move(ci));
> > > + VERIFY( q.as_const && ! q.as_lvalue );
> > > }
> > >
> > > void
> > > @@ -167,11 +269,51 @@ test04()
> > > VERIFY( bind_front(g2, 3)() == 6 );
> > > }
> > >
> > > +struct CountedArg
> > > +{
> > > + CountedArg() = default;
> > > + CountedArg(CountedArg&& f) noexcept : counter(f.counter) {
> ++counter; }
> > > + CountedArg& operator=(CountedArg&&) = delete;
> > > +
> > > + int counter = 0;
> > > +};
> > > +CountedArg const c;
> > > +
> > > +void
> > > +testMaterialization()
> > > +{
> > > + struct F
> > > + {
> > > + int operator()(int, CountedArg arg) const
> > > + { return arg.counter; };
> > > + };
> > > +
> > > + // CountedArg is bound to rvalue-reference thus moved
> > > + auto f0 = std::bind_front(F{});
> > > + VERIFY( f0(10, CountedArg()) == 1 );
> > > +
> > > + auto f1 = std::bind_front(F{}, 10);
> > > + VERIFY( f1(CountedArg()) == 1 );
> > > +}
> > > +
> > > int
> > > main()
> > > {
> > > test01();
> > > - test02();
> > > test03();
> > > test04();
> > > +
> > > + testTarget();
> > > + testTarget(10);
> > > + testTarget(10, 20, 30);
> > > +
> > > + testBoundArgs();
> > > + testBoundArgs(10);
> > > + testBoundArgs(10, 20, 30);
> > > +
> > > + testCallArgs();
> > > + testCallArgs(10);
> > > + testCallArgs(10, 20, 30);
> > > +
> > > + testMaterialization();
> > > }
> > > diff --git
> > > a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> > > b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> > > index 5fe0a83baec..6694322d67e 100644
> > > ---
> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> > > +++
> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> > > @@ -37,6 +37,17 @@ int main() {
> > > g1(); // { dg-error "deleted|no match" }
> > > std::move(g1)(); // { dg-error "deleted|no match" }
> > > std::move(std::as_const(g1))();
> > > +
> > > + auto f2 = std::bind_front(F{}, 42, 10);
> > > + f2(); // { dg-error "deleted|no match" }
> > > + std::move(f2)();
> > > + std::as_const(f2)();
> > > + std::move(std::as_const(f2))();
> > > +
> > > + auto g2 = std::bind_front(G{}, 42, 10);
> > > + g2(); // { dg-error "deleted|no match" }
> > > + std::move(g2)(); // { dg-error "deleted|no match" }
> > > + std::move(std::as_const(g2))();
> > > }
> > >
> > > // { dg-error "no type named 'type' in 'struct std::invoke_result" "" {
> > > target c++23 } 0 }
> > > --
> > > 2.50.1
> > >
> > >
> >
> >
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250826/5d2c33b5/attachment-0001.htm>
More information about the Libstdc++
mailing list