https://gcc.gnu.org/g:f27b8040ec8f2273cffbce7b66e94c323c53c32c
commit r16-4601-gf27b8040ec8f2273cffbce7b66e94c323c53c32c Author: Tomasz KamiĆski <[email protected]> Date: Fri Oct 24 09:37:13 2025 +0200 libstdc++: Forward arguments for bind_front<f>,bind_back<f>,nttp<f> [PR122022] This patch fixes a missing forwarding-reference (&&) in _Bind_fn_t::operator() and lambda returned from not_fn<f>. The bind_front<f>/bind_back<f> tests were updated to use a structure similar to r16-3398-g250dd5b5604fbc to cover cases involving zero, one, and many bound arguments. PR libstdc++/122022 libstdc++-v3/ChangeLog: * include/std/functional (_Bind_fn_t): Use forwarding reference as paremeter. (std::not_fn<f>): Use forwarding reference as lambda parameter. * testsuite/20_util/function_objects/bind_back/nttp.cc: Rework tests. * testsuite/20_util/function_objects/bind_front/nttp.cc: Likewise. * testsuite/20_util/function_objects/not_fn/nttp.cc: Add test for argument forwarding. Diff: --- libstdc++-v3/include/std/functional | 8 +- .../20_util/function_objects/bind_back/nttp.cc | 244 ++++++++++++-------- .../20_util/function_objects/bind_front/nttp.cc | 248 +++++++++++++-------- .../20_util/function_objects/not_fn/nttp.cc | 47 ++++ 4 files changed, 366 insertions(+), 181 deletions(-) diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional index 8ad73b343bda..dd1aa204eae5 100644 --- a/libstdc++-v3/include/std/functional +++ b/libstdc++-v3/include/std/functional @@ -928,10 +928,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { using _Fn = const decltype(__fn)&; template <typename... _Args> + requires is_invocable_v<_Fn, _Args...> constexpr static decltype(auto) - operator()(_Args... __args) - noexcept(is_nothrow_invocable_v<_Fn, _Args...>) - requires is_invocable_v<_Fn, _Args...> + operator()(_Args&&... __args) + noexcept(is_nothrow_invocable_v<_Fn, _Args...>) { return std::invoke(__fn, std::forward<_Args>(__args)...); } }; #endif @@ -1188,7 +1188,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION using _Fn = decltype(__fn); if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>) static_assert(__fn != nullptr); - return []<typename... _Args>(_Args... __args) static + return []<typename... _Args>(_Args&&... __args) static noexcept(noexcept( !std::invoke(__fn, std::forward<_Args>(__args)...) )) -> decltype(auto) 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 4dff909a3873..3bea8eced43e 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 @@ -39,101 +39,145 @@ 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 }; } }; + constexpr F f; + auto g = bind_back<f>(args...); + const auto& cg = g; + quals q; + + // template parameter object is always constant lvalue + 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()(int, int) & { return { false, true }; } - quals operator()(int, int) const & { return { true, true }; } - quals operator()(int, int) && { return { false, false }; } - quals operator()(int, int) 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 }; } }; - // Constness and value category forwarded to the target object? - { // no bound args - constexpr F f; - auto g = bind_back<f>(); - const auto& cg = g; - quals q; - - q = g(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(g)(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = cg(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(cg)(0,0); - VERIFY( q.as_const && q.as_lvalue ); - } - { // one bound arg - constexpr F f; - auto g = bind_back<f>(0); - const auto& cg = g; - quals q; - - q = g(0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(g)(0); - VERIFY( q.as_const && q.as_lvalue ); - q = cg(0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(cg)(0); - VERIFY( q.as_const && q.as_lvalue ); - } - { // two bound args, the general case - constexpr F f; - auto g = bind_back<f>(0,0); - const auto& cg = g; - quals q; - - 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 ); - } + constexpr F f; + auto g = bind_back<f>(args..., 10); + const auto& cg = g; + quals q; + + // constness and value category should be forwarded to the bound objects: + 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 ); + + int i = 0; + auto gr = bind_back<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_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 -test02a() +testCallArgs(Args... args) { - struct quals - { - bool as_const; - bool as_lvalue; - }; struct F { - quals operator()(int, int&) const { return { false, true }; } - quals operator()(int, int const&) const { return { true, true }; } - quals operator()(int, int&&) const { return { false, false }; } - quals operator()(int, int const&&) const { return { true, false }; } + 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 }; } }; - constexpr F f{}; - // verify propagation - auto h = bind_back<f>(10); - auto const& ch = h; + constexpr F f; + auto g = bind_back<f>(args...); + const auto& cg = g; quals q; - - q = h(0); - VERIFY( !q.as_const && q.as_lvalue ); - q = ch(0); + 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 = std::move(h)(0); - VERIFY( !q.as_const && !q.as_lvalue ); - q = std::move(ch)(0); - VERIFY( q.as_const && !q.as_lvalue ); + q = cg(std::move(ci)); + VERIFY( q.as_const && ! q.as_lvalue ); + + struct S + { + int operator()(long, long, Args...) const { return 1; } + int operator()(int, void*, Args...) const { return 2; } + }; + + constexpr S s; + // literal zero can be converted to any pointer, so (int, void*) + // is best candidate + VERIFY( s(0, 0, args...) == 2 ); + // both arguments are bound to int&&, and no longer can be + // converted to pointer, (long, long) is only candidate + VERIFY( bind_back<s>()(0, 0, args...) == 1 ); + VERIFY( bind_back<s>(args...)(0, 0) == 1 ); } void @@ -233,26 +277,52 @@ test04() return true; } -struct C { int i = 0; }; -struct D : C { D(){} D(D&&) { ++i; } }; -int f5(D const& d1, D const& d2, D const& d3) -{ return d1.i + d2.i + d3.i; } +struct CountedArg +{ + CountedArg() = default; + CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; } + CountedArg& operator=(CountedArg&&) = delete; + + int counter = 0; +}; +CountedArg const c; -void test05() +void +testMaterialization() { - // Must move arguments into capture object, not construct in place - // like normal arguments. - VERIFY( bind_back<f5>(D{}, D{})(D{}) == 2 ); + 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(); - test02a(); test03(); test03a(); static_assert(test04()); - test05(); + + 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/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc index 0839c849c0ce..9eb3c432a86b 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 @@ -39,104 +39,145 @@ test01() >); } -void -test02() +struct quals { - struct quals - { - bool as_const; - bool as_lvalue; - }; + bool as_const; + bool as_lvalue; +}; +template<typename... Args> +void +testTarget(Args... args) +{ struct F { - quals operator()(int, int) & { return { false, true }; } - quals operator()(int, int) const & { return { true, true }; } - quals operator()(int, int) && { return { false, false }; } - quals operator()(int, int) const && { return { true, false }; } + 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 }; } }; - // Constness and value category forwarded to the target object? - { // no bound args - constexpr F f; - auto g = bind_front<f>(); - const auto& cg = g; - quals q; - - // Constness and value category forwarded to the target object? - q = g(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(g)(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = cg(0,0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(cg)(0,0); - VERIFY( q.as_const && q.as_lvalue ); - } - { // one bound arg (for when we implement that as a separate case) - constexpr F f; - auto g = bind_front<f>(0); - const auto& cg = g; - quals q; - - // Constness and value category forwarded to the target object? - q = g(0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(g)(0); - VERIFY( q.as_const && q.as_lvalue ); - q = cg(0); - VERIFY( q.as_const && q.as_lvalue ); - q = std::move(cg)(0); - VERIFY( q.as_const && q.as_lvalue ); - } - { // two bound args, the general case - constexpr F f; - auto g = bind_front<f>(0,0); - const auto& cg = g; - quals q; - - 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 ); - } + constexpr F f; + auto g = bind_front<f>(args...); + const auto& cg = g; + quals q; + + // template parameter object is always constant lvalue + 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 -test02a() +testBoundArgs(Args... args) { - struct quals + struct F { - bool as_const; - bool as_lvalue; + 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 }; } }; + constexpr F f; + auto g = bind_front<f>(args..., 10); + const auto& cg = g; + quals q; + + // constness and value category should be forwarded to the bound objects: + 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 ); + + 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()(int&, int) const { return { false, true }; } - quals operator()(int const&, int) const { return { true, true }; } - quals operator()(int&&, int) const { return { false, false }; } - quals operator()(int const&&, int) 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 }; } }; - constexpr F f{}; - // verify propagation - auto h = bind_front<f>(10); - auto const& ch = h; + constexpr F f; + auto g = bind_front<f>(args...); + const auto& cg = g; quals q; - - q = h(0); - VERIFY( !q.as_const && q.as_lvalue ); - q = ch(0); + 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 = std::move(h)(0); - VERIFY( !q.as_const && !q.as_lvalue ); - q = std::move(ch)(0); - VERIFY( q.as_const && !q.as_lvalue ); + q = cg(std::move(ci)); + VERIFY( q.as_const && ! q.as_lvalue ); + + struct S + { + int operator()(Args..., long, long) const { return 1; } + int operator()(Args..., int, void*) const { return 2; } + }; + + constexpr S s; + // literal zero can be converted to any pointer, so (int, void*) + // is best candidate + VERIFY( s(args..., 0, 0) == 2 ); + // both arguments are bound to int&&, and no longer can be + // converted to pointer, (long, long) is only candidate + VERIFY( bind_front<s>()(args..., 0, 0) == 1 ); + VERIFY( bind_front<s>(args...)(0, 0) == 1 ); } void @@ -235,26 +276,53 @@ test04() return true; } -struct C { int i = 0; }; -struct D : C { D(){} D(D&&) { ++i; } }; -int f5(D const& d1, D const& d2, D const& d3) -{ return d1.i + d2.i + d3.i; } +struct CountedArg +{ + CountedArg() = default; + CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; } + CountedArg& operator=(CountedArg&&) = delete; + + int counter = 0; +}; +CountedArg const c; -void test05() +void +testMaterialization() { - // Must move arguments into capture object, not construct in place - // like normal arguments. - VERIFY( bind_front<f5>(D{}, D{})(D{}) == 2 ); + 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(); - test02a(); test03(); test03a(); static_assert(test04()); - test05(); + + 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/not_fn/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc index d24ccf8a1872..3567d679a775 100644 --- a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc @@ -81,6 +81,52 @@ test07() static_assert( !std::is_invocable<NotF>::value, "cannot negate" ); } +void +test08() +{ + struct quals + { + bool as_const; + bool as_lvalue; + + quals operator!() const + { return *this; }; + }; + + struct F + { + quals operator()(int&) const { return { false, true }; } + quals operator()(int const&) const { return { true, true }; } + quals operator()(int&&) const { return { false, false }; } + quals operator()(int const&&) const { return { true, false }; } + }; + + constexpr F f; + auto g = not_fn<f>(); + 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 ); +} + int main() { @@ -89,6 +135,7 @@ main() test05(); test06(); test07(); + test08(); constexpr auto f = []{ return false; }; static_assert(std::not_fn<f>()()); }
