From 250dd5b5604fbc9149e30f6b9cfaabdd600592e7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomasz=20Kami=C5=84ski?= Date: Tue, 19 Aug 2025 15:32:47 +0200 Subject: [PATCH] libstdc++: Refactor bound arguments storage for bind_front/back MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 otherwise. _Bound_arg_storage is less expensive to instantiate and access than std::tuple. It can also be trivially copyable, as it doesn't require a non-trivial assignment operator for reference types. Storing a single argument directly provides similar benefits compared to both one element tuple or _Bound_arg_storage. _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. Reviewed-by: Jonathan Wakely Reviewed-by: Patrick Palka Signed-off-by: Tomasz Kamiński --- 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 061e6b4de3d8..8c4f461a1105 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 struct __like_impl; // _Tp must be a reference and _Up an lvalue reference @@ -112,6 +111,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template 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 307bcb95bcca..b1cda87929df 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 + struct _Indexed_bound_arg + { + [[no_unique_address]] _Tp _M_val; + }; + + template + struct _Bound_arg_storage : _IndexedArgs... + { + template + 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 + 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 + 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 = [&](index_sequence<_Inds...>) + { + return _Bound_arg_storage<_Indexed_bound_arg<_Inds, _BoundArgs>...> + { {_BoundArgs(std::forward<_Args>(__args))}... }; + }; + return __impl(index_sequence_for<_BoundArgs...>()); + } + } template struct _Bind_front @@ -937,7 +984,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(__and_, 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 @@ -971,8 +1017,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(is_nothrow_invocable_v) { - return _S_call(*this, _BoundIndices(), - std::forward<_CallArgs>(__call_args)...); + return _S_call(*this, std::forward<_CallArgs>(__call_args)...); } template @@ -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 @@ -994,8 +1039,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(is_nothrow_invocable_v) { - return _S_call(std::move(*this), _BoundIndices(), - std::forward<_CallArgs>(__call_args)...); + return _S_call(std::move(*this), + std::forward<_CallArgs>(__call_args)...); } template @@ -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 + template 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 @@ -1066,7 +1119,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION noexcept(__and_, 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 @@ -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 + template 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 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 c31d32288156..a31528fc755f 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(), std::declval())) >); + static_assert(std::is_same_v< + decltype(bind_back(std::declval(), std::declval(), std::declval())), + decltype(bind_back(std::declval(), std::declval(), std::declval())) + >); + static_assert(std::is_same_v< + decltype(bind_back(std::declval(), std::declval(), std::declval())), + decltype(bind_back(std::declval(), std::declval(), std::declval())) + >); + + // Reference wrappers should be handled: static_assert(!std::is_same_v< decltype(bind_back(std::declval(), std::declval())), @@ -63,29 +73,58 @@ test01() >); } +struct quals +{ + bool as_const; + bool as_lvalue; +}; + +template 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 +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 +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 d634db9dc1d0..de3ae47e37f7 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 57482c522639..ef28de8321b1 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(), std::declval())) >); + static_assert(std::is_same_v< + decltype(bind_front(std::declval(), std::declval(), std::declval())), + decltype(bind_front(std::declval(), std::declval(), std::declval())) + >); + static_assert(std::is_same_v< + decltype(bind_front(std::declval(), std::declval(), std::declval())), + decltype(bind_front(std::declval(), std::declval(), std::declval())) + >); + // Reference wrappers should be handled: static_assert(!std::is_same_v< decltype(bind_front(std::declval(), std::declval())), @@ -63,29 +72,58 @@ test01() >); } +struct quals +{ + bool as_const; + bool as_lvalue; +}; + +template 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 +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 +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 5fe0a83baec2..6694322d67e7 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.47.3