On Fri, 24 Oct 2025 at 10:54, Tomasz Kamiński <[email protected]> wrote:
>
> 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.
> ---
> v2 mentions the PR122022 and extend fix to not_fn.
OK, thanks
>
> libstdc++-v3/include/std/functional | 8 +-
> .../function_objects/bind_back/nttp.cc | 244 +++++++++++------
> .../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 8ad73b343bd..dd1aa204eae 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 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();
> +
> }
> 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 d24ccf8a187..3567d679a77 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>()());
> }
> --
> 2.51.0
>