]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Forward arguments for bind_front<f>,bind_back<f>,nttp<f> [PR122022]
authorTomasz Kamiński <tkaminsk@redhat.com>
Fri, 24 Oct 2025 07:37:13 +0000 (09:37 +0200)
committerTomasz Kamiński <tkaminsk@redhat.com>
Fri, 24 Oct 2025 12:41:28 +0000 (14:41 +0200)
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.

libstdc++-v3/include/std/functional
libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc

index 8ad73b343bda41e70d0bf52731adeda40c255f59..dd1aa204eae5a21419b0c2a6121045c01aa5e82a 100644 (file)
@@ -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)
index 4dff909a3873dbacc3b42a071c0dd128d2991e31..3bea8eced43e68cec0dea547480b910f7f4dbf64 100644 (file)
@@ -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();
 }
index 0839c849c0cece7377be796009d27e3df18972ab..9eb3c432a86b24f53d39503f4de0169899db1693 100644 (file)
@@ -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();
 }
index d24ccf8a1872e244dcdfa4a15303a284d4a44793..3567d679a775111e1247c60193ef8482bed16686 100644 (file)
@@ -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>()());
 }