On Fri, Oct 24, 2025 at 11:15 AM Tomasz Kamiński <[email protected]> wrote:
> This patch fixes a missing forwarding-reference (&&) in > _Bind_fn_t::operator(). > This issue was detected when 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. > > libstdc++-v3/ChangeLog: > > * include/std/functional (_Bind_fn_t): Use forwarding reference > as paremeter. > * testsuite/20_util/function_objects/bind_back/nttp.cc: Rework > tests. > * testsuite/20_util/function_objects/bind_front/nttp.cc: Likewise. > --- > Tested on x86_64-linux. OK for trunk? > This turned out to be already reported: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122022. I will update commit message accordingly. > > libstdc++-v3/include/std/functional | 6 +- > .../function_objects/bind_back/nttp.cc | 244 +++++++++++------ > .../function_objects/bind_front/nttp.cc | 248 +++++++++++------- > 3 files changed, 318 insertions(+), 180 deletions(-) > > diff --git a/libstdc++-v3/include/std/functional > b/libstdc++-v3/include/std/functional > index 8ad73b343bd..e4dbb9e0c45 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 > 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 4dff909a387..3bea8eced43 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 0839c849c0c..9eb3c432a86 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(); > + > } > -- > 2.51.0 > >
