On Tue, 26 Aug 2025, Tomasz Kaminski wrote: > > > On Tue, Aug 26, 2025 at 5:50 PM Patrick Palka <ppa...@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 could do that for the primary template, but _Partial also has partial specializations for "simple" bound arguments (e.g. views::take(5)) where perfect forwarding isn't observable and so we don't need multiple operator() overloads in C++20 mode. This is a nice compile-time optimization and diagnostic improvement that we probably should keep around. > 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 <tkami...@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 > > > > > > > > > > > > >