[libcxx-commits] [libcxx] [libc++] Implement `bind_back` (PR #81055)

Jakub Mazurkiewicz via libcxx-commits libcxx-commits at lists.llvm.org
Thu Mar 14 06:30:36 PDT 2024


https://github.com/JMazurkiewicz updated https://github.com/llvm/llvm-project/pull/81055

>From dcda573def3bc2b0155e136c37f68f10f8fff5cc Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Tue, 27 Feb 2024 16:13:00 +0100
Subject: [PATCH 1/6] [libc++] Implement `bind_back`

Implement `std::bind_back` function from P2387R3 "Pipe support for user-defined range adaptors".
---
 libcxx/docs/FeatureTestMacroTable.rst         |   4 +-
 libcxx/docs/Status/Cxx23.rst                  |   1 +
 libcxx/docs/Status/Cxx23Papers.csv            |   2 +-
 libcxx/include/__functional/bind_back.h       |  14 +
 libcxx/include/functional                     |   6 +
 libcxx/include/version                        |   7 +-
 libcxx/modules/std/functional.inc             |   4 +-
 .../functional.version.compile.pass.cpp       |  33 +-
 .../version.version.compile.pass.cpp          |  33 +-
 .../func.bind.partial/bind_back.pass.cpp      | 365 ++++++++++++++++++
 .../func.bind.partial/bind_back.verify.cpp    |  80 ++++
 .../func.bind.partial/types.h                 |  45 +++
 .../generate_feature_test_macro_components.py |   3 +-
 13 files changed, 539 insertions(+), 58 deletions(-)
 create mode 100644 libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.bind.partial/types.h

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 468226c0c2dddf..04567aa3c664aa 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -308,7 +308,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_associative_heterogeneous_erasure``     *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_bind_back``                             *unimplemented*
+    ``__cpp_lib_bind_back``                             ``202202L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_byteswap``                              ``202110L``
     --------------------------------------------------- -----------------
@@ -392,8 +392,6 @@ Status
     ---------------------------------------------------------------------
     ``__cpp_lib_associative_heterogeneous_insertion``   *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_bind_back``                             *unimplemented*
-    --------------------------------------------------- -----------------
     ``__cpp_lib_bind_front``                            ``202306L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_bitset``                                ``202306L``
diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 3e6e33f08c7ccf..a3a0d8b81e88f9 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -43,6 +43,7 @@ Paper Status
    .. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
    .. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
       clang doesn't issue a diagnostic for deprecated using template declarations.
+   .. [#note-P2387R3] P2387R3: ``bind_back`` only
    .. [#note-P2520R0] P2520R0: Libc++ implemented this paper as a DR in C++20 as well.
    .. [#note-P2711R1] P2711R1: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
    .. [#note-P2770R0] P2770R0: ``join_with_view`` hasn't been done yet since this type isn't implemented yet.
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index eb415ed8c031fa..aebc2ffe5b067a 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -45,7 +45,7 @@
 "`P1413R3 <https://wg21.link/P1413R3>`__","LWG","Deprecate ``std::aligned_storage`` and ``std::aligned_union``","February 2022","|Complete| [#note-P1413R3]_",""
 "`P2255R2 <https://wg21.link/P2255R2>`__","LWG","A type trait to detect reference binding to temporary","February 2022","",""
 "`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0"
-"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","","","|ranges|"
+"`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","|Partial| [#note-P2387R3]_","","|ranges|"
 "`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|"
 "`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","|In Progress|","","|ranges|"
 "`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
diff --git a/libcxx/include/__functional/bind_back.h b/libcxx/include/__functional/bind_back.h
index ce26d3b70630f3..3c42d4769e8a9f 100644
--- a/libcxx/include/__functional/bind_back.h
+++ b/libcxx/include/__functional/bind_back.h
@@ -62,6 +62,20 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto __bind_back(_Fn&& __f, _Args&&... __args) n
       std::forward<_Fn>(__f), std::forward_as_tuple(std::forward<_Args>(__args)...));
 }
 
+#  if _LIBCPP_STD_VER >= 23
+template <class _Fn, class... _Args>
+_LIBCPP_HIDE_FROM_ABI constexpr auto bind_back(_Fn&& __f, _Args&&... __args) {
+  static_assert(is_constructible_v<decay_t<_Fn>, _Fn>, "bind_back requires decay_t<F> to be constructible from F");
+  static_assert(is_move_constructible_v<decay_t<_Fn>>, "bind_back requires decay_t<F> to be move constructible");
+  static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...),
+                "bind_back requires all decay_t<Args> to be constructible from respective Args");
+  static_assert((is_move_constructible_v<decay_t<_Args>> && ...),
+                "bind_back requires all decay_t<Args> to be move constructible");
+  return __bind_back_t<decay_t<_Fn>, tuple<decay_t<_Args>...>>(
+      std::forward<_Fn>(__f), std::forward_as_tuple(std::forward<_Args>(__args)...));
+}
+#  endif // _LIBCPP_STD_VER >= 23
+
 #endif // _LIBCPP_STD_VER >= 20
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/functional b/libcxx/include/functional
index fd99e11fb18180..d72ebb2310c5ae 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -207,6 +207,12 @@ binary_negate<Predicate> not2(const Predicate& pred);
 template <class F>
 constexpr unspecified not_fn(F&& f); // C++17, constexpr in C++20
 
+// [func.bind.partial], function templates bind_front and bind_back
+template<class F, class... Args>
+  constexpr unspecified bind_front(F&&, Args&&...); // C++20
+template<class F, class... Args>
+  constexpr unspecified bind_back(F&&, Args&&...);  // C++23
+
 template<class T> struct is_bind_expression;
 template<class T> struct is_placeholder;
 
diff --git a/libcxx/include/version b/libcxx/include/version
index b18927a2bc38c2..bd97bda509f91e 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -40,8 +40,7 @@ __cpp_lib_atomic_shared_ptr                             201711L <atomic>
 __cpp_lib_atomic_value_initialization                   201911L <atomic> <memory>
 __cpp_lib_atomic_wait                                   201907L <atomic>
 __cpp_lib_barrier                                       201907L <barrier>
-__cpp_lib_bind_back                                     202306L <functional>
-                                                        202202L // C++23
+__cpp_lib_bind_back                                     202202L <functional>
 __cpp_lib_bind_front                                    202306L <functional>
                                                         201907L // C++20
 __cpp_lib_bit_cast                                      201806L <bit>
@@ -439,7 +438,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_adaptor_iterator_pair_constructor    202106L
 # define __cpp_lib_allocate_at_least                    202302L
 // # define __cpp_lib_associative_heterogeneous_erasure    202110L
-// # define __cpp_lib_bind_back                            202202L
+# define __cpp_lib_bind_back                            202202L
 # define __cpp_lib_byteswap                             202110L
 # define __cpp_lib_constexpr_bitset                     202207L
 # define __cpp_lib_constexpr_charconv                   202207L
@@ -485,8 +484,6 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 
 #if _LIBCPP_STD_VER >= 26
 // # define __cpp_lib_associative_heterogeneous_insertion  202306L
-# undef  __cpp_lib_bind_back
-// # define __cpp_lib_bind_back                            202306L
 # undef  __cpp_lib_bind_front
 # define __cpp_lib_bind_front                           202306L
 # define __cpp_lib_bitset                               202306L
diff --git a/libcxx/modules/std/functional.inc b/libcxx/modules/std/functional.inc
index 1148944a9d2fee..ddc7d023ee6dc2 100644
--- a/libcxx/modules/std/functional.inc
+++ b/libcxx/modules/std/functional.inc
@@ -56,8 +56,10 @@ export namespace std {
   using std::not_fn;
 
   // [func.bind.partial], function templates bind_front and bind_back
-  // using std::bind_back;
   using std::bind_front;
+#if _LIBCPP_STD_VER >= 23
+  using std::bind_back;
+#endif
 
   // [func.bind], bind
   using std::is_bind_expression;
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
index 72c96c62b64c45..6895afb2fb2969 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
@@ -17,7 +17,6 @@
 
 /*  Constant                           Value
     __cpp_lib_bind_back                202202L [C++23]
-                                       202306L [C++26]
     __cpp_lib_bind_front               201907L [C++20]
                                        202306L [C++26]
     __cpp_lib_boyer_moore_searcher     201603L [C++17]
@@ -320,17 +319,11 @@
 
 #elif TEST_STD_VER == 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should be defined in c++23"
-#   endif
-#   if __cpp_lib_bind_back != 202202L
-#     error "__cpp_lib_bind_back should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_bind_back
+#   error "__cpp_lib_bind_back should be defined in c++23"
+# endif
+# if __cpp_lib_bind_back != 202202L
+#   error "__cpp_lib_bind_back should have the value 202202L in c++23"
 # endif
 
 # ifndef __cpp_lib_bind_front
@@ -426,17 +419,11 @@
 
 #elif TEST_STD_VER > 23
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should be defined in c++26"
-#   endif
-#   if __cpp_lib_bind_back != 202306L
-#     error "__cpp_lib_bind_back should have the value 202306L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_bind_back
+#   error "__cpp_lib_bind_back should be defined in c++26"
+# endif
+# if __cpp_lib_bind_back != 202202L
+#   error "__cpp_lib_bind_back should have the value 202202L in c++26"
 # endif
 
 # ifndef __cpp_lib_bind_front
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 14271308624e65..533353662e3a5f 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -38,7 +38,6 @@
     __cpp_lib_atomic_wait                            201907L [C++20]
     __cpp_lib_barrier                                201907L [C++20]
     __cpp_lib_bind_back                              202202L [C++23]
-                                                     202306L [C++26]
     __cpp_lib_bind_front                             201907L [C++20]
                                                      202306L [C++26]
     __cpp_lib_bit_cast                               201806L [C++20]
@@ -4465,17 +4464,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should be defined in c++23"
-#   endif
-#   if __cpp_lib_bind_back != 202202L
-#     error "__cpp_lib_bind_back should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_bind_back
+#   error "__cpp_lib_bind_back should be defined in c++23"
+# endif
+# if __cpp_lib_bind_back != 202202L
+#   error "__cpp_lib_bind_back should have the value 202202L in c++23"
 # endif
 
 # ifndef __cpp_lib_bind_front
@@ -6053,17 +6046,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should be defined in c++26"
-#   endif
-#   if __cpp_lib_bind_back != 202306L
-#     error "__cpp_lib_bind_back should have the value 202306L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_bind_back
-#     error "__cpp_lib_bind_back should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_bind_back
+#   error "__cpp_lib_bind_back should be defined in c++26"
+# endif
+# if __cpp_lib_bind_back != 202202L
+#   error "__cpp_lib_bind_back should have the value 202202L in c++26"
 # endif
 
 # ifndef __cpp_lib_bind_front
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
new file mode 100644
index 00000000000000..e16c33bf1422e7
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
@@ -0,0 +1,365 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <functional>
+
+// template<class F, class... Args>
+//   constexpr unspecified bind_back(F&& f, Args&&... args);
+
+#include <functional>
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "callable_types.h"
+#include "types.h"
+
+template <class Fn, class... Args>
+concept back_bindable =
+    requires(Fn&& fn, Args&&... args) { std::bind_back(std::forward<Fn>(fn), std::forward<Args>(args)...); };
+
+constexpr void test_basic_bindings() {
+  { // Bind arguments, call without arguments
+    {
+      auto f = std::bind_back(MakeTuple{});
+      assert(f() == std::make_tuple());
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{});
+      assert(f() == std::make_tuple(Elem<1>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{});
+      assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+    }
+  }
+
+  { // Bind no arguments, call with arguments
+    {
+      auto f = std::bind_back(MakeTuple{});
+      assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{});
+      assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{});
+      assert(f(Elem<1>{}, Elem<2>{}, Elem<3>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+    }
+  }
+
+  { // Bind arguments, call with arguments
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{}, Elem<2>{}, Elem<3>{}));
+    }
+
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{}, Elem<2>{}, Elem<3>{}));
+    }
+    {
+      auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}, Elem<11>{}, Elem<12>{}) ==
+             std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<12>{}, Elem<1>{}, Elem<2>{}, Elem<3>{}));
+    }
+  }
+
+  { // Basic tests with fundamental types
+    int n      = 2;
+    int m      = 1;
+    auto add   = [](int x, int y) { return x + y; };
+    auto add_n = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+
+    auto a = std::bind_back(add, m, n);
+    assert(a() == 3);
+
+    auto b = std::bind_back(add_n, m, n, m, m, m, m);
+    assert(b() == 7);
+
+    auto c = std::bind_back(add_n, n, m);
+    assert(c(1, 1, 1, 1) == 7);
+
+    auto f = std::bind_back(add, n);
+    assert(f(3) == 5);
+
+    auto g = std::bind_back(add, n, 1);
+    assert(g() == 3);
+
+    auto h = std::bind_back(add_n, 1, 1, 1);
+    assert(h(2, 2, 2) == 9);
+  }
+}
+
+constexpr void test_edge_cases() {
+  { // Make sure we don't treat std::reference_wrapper specially.
+    auto sub = [](std::reference_wrapper<int> a, std::reference_wrapper<int> b) { return a.get() - b.get(); };
+
+    int i  = 1;
+    int j  = 2;
+    auto f = std::bind_back(sub, std::ref(i));
+    assert(f(std::ref(j)) == 1);
+  }
+
+  { // Make sure we can call a function that's a pointer to a member function.
+    struct MemberFunction {
+      constexpr int foo(int x, int y) { return x * y; }
+    };
+
+    MemberFunction value;
+    auto fn = std::bind_back(&MemberFunction::foo, 2, 3);
+    assert(fn(value) == 6);
+  }
+
+  { // Make sure we can call a function that's a pointer to a member object.
+    struct MemberObject {
+      int obj;
+    };
+
+    MemberObject value{.obj = 3};
+    auto fn = std::bind_back(&MemberObject::obj);
+    assert(fn(value) == 3);
+  }
+}
+
+constexpr void test_passing_arguments() {
+  { // Make sure that we copy the bound arguments into the unspecified-type.
+    auto add = [](int x, int y) { return x + y; };
+    int n    = 2;
+    auto f   = std::bind_back(add, n, 1);
+    n        = 100;
+    assert(f() == 3);
+  }
+
+  { // Make sure we pass the bound arguments to the function object
+    // with the right value category.
+    {
+      auto was_copied = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::copy; };
+      CopyMoveInfo info;
+      auto f = std::bind_back(was_copied, info);
+      assert(f());
+    }
+
+    {
+      auto was_moved = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::move; };
+      CopyMoveInfo info;
+      auto f = std::bind_back(was_moved, info);
+      assert(std::move(f)());
+    }
+  }
+}
+
+constexpr void test_function_objects() {
+  { // Make sure we call the correctly cv-ref qualified operator()
+    // based on the value category of the bind_back unspecified-type.
+    struct X {
+      constexpr int operator()() & { return 1; }
+      constexpr int operator()() const& { return 2; }
+      constexpr int operator()() && { return 3; }
+      constexpr int operator()() const&& { return 4; }
+    };
+
+    auto f  = std::bind_back(X{});
+    using F = decltype(f);
+    assert(static_cast<F&>(f)() == 1);
+    assert(static_cast<const F&>(f)() == 2);
+    assert(static_cast<F&&>(f)() == 3);
+    assert(static_cast<const F&&>(f)() == 4);
+  }
+
+  // Make sure the `bind_back` unspecified-type does not model invocable
+  // when the call would select a differently-qualified operator().
+  //
+  // For example, if the call to `operator()() &` is ill-formed, the call to the unspecified-type
+  // should be ill-formed and not fall back to the `operator()() const&` overload.
+  { // Make sure we delete the & overload when the underlying call isn't valid.
+    {
+      struct X {
+        void operator()() & = delete;
+        void operator()() const&;
+        void operator()() &&;
+        void operator()() const&&;
+      };
+
+      using F = decltype(std::bind_back(X{}));
+      static_assert(!std::invocable<F&>);
+      static_assert(std::invocable<const F&>);
+      static_assert(std::invocable<F>);
+      static_assert(std::invocable<const F>);
+    }
+
+    // There's no way to make sure we delete the const& overload when the underlying call isn't valid,
+    // so we can't check this one.
+
+    { // Make sure we delete the && overload when the underlying call isn't valid.
+      struct X {
+        void operator()() &;
+        void operator()() const&;
+        void operator()() && = delete;
+        void operator()() const&&;
+      };
+
+      using F = decltype(std::bind_back(X{}));
+      static_assert(std::invocable<F&>);
+      static_assert(std::invocable<const F&>);
+      static_assert(!std::invocable<F>);
+      static_assert(std::invocable<const F>);
+    }
+
+    { // Make sure we delete the const&& overload when the underlying call isn't valid.
+      struct X {
+        void operator()() &;
+        void operator()() const&;
+        void operator()() &&;
+        void operator()() const&& = delete;
+      };
+
+      using F = decltype(std::bind_back(X{}));
+      static_assert(std::invocable<F&>);
+      static_assert(std::invocable<const F&>);
+      static_assert(std::invocable<F>);
+      static_assert(!std::invocable<const F>);
+    }
+  }
+
+  { // Extra value category tests
+    struct X {};
+
+    {
+      struct Y {
+        void operator()(X&&) const&;
+        void operator()(X&&) && = delete;
+      };
+
+      using F = decltype(std::bind_back(Y{}));
+      static_assert(std::invocable<F&, X>);
+      static_assert(!std::invocable<F, X>);
+    }
+
+    {
+      struct Y {
+        void operator()(const X&) const;
+        void operator()(X&&) const = delete;
+      };
+
+      using F = decltype(std::bind_back(Y{}, X{}));
+      static_assert(std::invocable<F&>);
+      static_assert(!std::invocable<F>);
+    }
+  }
+}
+
+constexpr void test_return_type() {
+  {   // Test properties of the constructor of the unspecified-type returned by bind_back.
+    { // Test move constructor when function is move only.
+      MoveOnlyCallable<bool> value(true);
+      auto f = std::bind_back(std::move(value), 1);
+      assert(f());
+      assert(f(1, 2, 3));
+
+      auto f1 = std::move(f);
+      assert(!f());
+      assert(f1());
+      assert(f1(1, 2, 3));
+
+      using F = decltype(f);
+      static_assert(std::is_move_constructible<F>::value);
+      static_assert(!std::is_copy_constructible<F>::value);
+      static_assert(!std::is_move_assignable<F>::value);
+      static_assert(!std::is_copy_assignable<F>::value);
+    }
+
+    { // Test move constructor when function is copyable but not assignable.
+      CopyCallable<bool> value(true);
+      auto f = std::bind_back(value, 1);
+      assert(f());
+      assert(f(1, 2, 3));
+
+      auto f1 = std::move(f);
+      assert(!f());
+      assert(f1());
+      assert(f1(1, 2, 3));
+
+      auto f2 = std::bind_back(std::move(value), 1);
+      assert(f1());
+      assert(f2());
+      assert(f2(1, 2, 3));
+
+      using F = decltype(f);
+      static_assert(std::is_move_constructible<F>::value);
+      static_assert(std::is_copy_constructible<F>::value);
+      static_assert(!std::is_move_assignable<F>::value);
+      static_assert(!std::is_copy_assignable<F>::value);
+    }
+
+    { // Test constructors when function is copy assignable.
+      using F = decltype(std::bind_back(std::declval<CopyAssignableWrapper&>(), 1));
+      static_assert(std::is_move_constructible<F>::value);
+      static_assert(std::is_copy_constructible<F>::value);
+      static_assert(std::is_move_assignable<F>::value);
+      static_assert(std::is_copy_assignable<F>::value);
+    }
+
+    { // Test constructors when function is move assignable only.
+      using F = decltype(std::bind_back(std::declval<MoveAssignableWrapper>(), 1));
+      static_assert(std::is_move_constructible<F>::value);
+      static_assert(!std::is_copy_constructible<F>::value);
+      static_assert(std::is_move_assignable<F>::value);
+      static_assert(!std::is_copy_assignable<F>::value);
+    }
+  }
+
+  { // Make sure bind_back's unspecified type's operator() is SFINAE-friendly.
+    using F = decltype(std::bind_back(std::declval<int (*)(int, int)>(), 1));
+    static_assert(!std::is_invocable<F>::value);
+    static_assert(std::is_invocable<F, int>::value);
+    static_assert(!std::is_invocable<F, void*>::value);
+    static_assert(!std::is_invocable<F, int, int>::value);
+  }
+}
+
+constexpr bool test() {
+  test_basic_bindings();
+  test_edge_cases();
+  test_passing_arguments();
+  test_function_objects();
+  test_return_type();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
new file mode 100644
index 00000000000000..f0841d39ca319f
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <functional>
+
+// template<class F, class... Args>
+//   constexpr unspecified bind_back(F&& f, Args&&... args);
+
+#include <functional>
+
+#include "types.h"
+
+void test() {
+  { // Various failures
+    auto p = std::bind_back(pass, 1);
+    static_assert(p() == 1); // expected-error {{static assertion expression is not an integral constant expression}}
+
+    auto d = std::bind_back(do_nothing, 2); // expected-error {{no matching function for call to 'bind_back'}}
+  }
+
+  { // Mandates: is_constructible_v<decay_t<F>, F>
+    struct F {
+      F()         = default;
+      F(const F&) = default;
+      F(F&)       = delete;
+
+      void operator()() {}
+    };
+
+    F f;
+    auto f1 = std::bind_back(f);
+    // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires decay_t<F> to be constructible from F}}
+  }
+
+  { // Mandates: is_move_constructible_v<decay_t<F>>
+    struct F {
+      F()         = default;
+      F(const F&) = default;
+      F(F&&)      = delete;
+
+      void operator()() {}
+    };
+
+    F f;
+    auto f1 = std::bind_back(f);
+    // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires decay_t<F> to be move constructible}}
+  }
+
+  { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...)
+    struct Arg {
+      Arg()           = default;
+      Arg(const Arg&) = default;
+      Arg(Arg&)       = delete;
+    };
+
+    Arg x;
+    auto f = std::bind_back([](const Arg&) {}, x);
+    // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires all decay_t<Args> to be constructible from respective Args}}
+    // expected-error@*:* {{no matching constructor for initialization}}
+  }
+
+  { // Mandates: (is_move_constructible_v<decay_t<Args>> && ...)
+    struct Arg {
+      Arg()           = default;
+      Arg(const Arg&) = default;
+      Arg(Arg&&)      = delete;
+    };
+
+    Arg x;
+    auto f = std::bind_back([](Arg&) {}, x);
+    // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires all decay_t<Args> to be move constructible}}
+  }
+}
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h b/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h
new file mode 100644
index 00000000000000..04e2061f7e78b8
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H
+#define TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H
+
+#include <tuple>
+#include <utility>
+
+struct MakeTuple {
+  template <class... Args>
+  constexpr auto operator()(Args&&... args) const {
+    return std::make_tuple(std::forward<Args>(args)...);
+  }
+};
+
+template <int X>
+struct Elem {
+  template <int Y>
+  constexpr bool operator==(const Elem<Y>&) const {
+    return X == Y;
+  }
+};
+
+struct CopyMoveInfo {
+  enum { none, copy, move } copy_kind;
+
+  constexpr CopyMoveInfo() : copy_kind(none) {}
+  constexpr CopyMoveInfo(const CopyMoveInfo&) : copy_kind(copy) {}
+  constexpr CopyMoveInfo(CopyMoveInfo&&) : copy_kind(move) {}
+};
+
+constexpr int pass(int n) { return n; }
+
+template <class T>
+T do_nothing(T t) {
+  return t;
+}
+
+#endif // TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index b688a30cdb792d..047fc89b28295e 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -205,10 +205,9 @@ def add_version_header(tc):
             "name": "__cpp_lib_bind_back",
             "values": {
                 "c++23": 202202,
-                "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
+                # "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
             },
             "headers": ["functional"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_bind_front",

>From 17913b9c8c914fafeacee4b2e8c988bd7afa71cc Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Tue, 27 Feb 2024 17:22:24 +0100
Subject: [PATCH 2/6] Clang modules: Fix build

---
 .../function.objects/func.bind.partial/bind_back.pass.cpp    | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
index e16c33bf1422e7..9c604c6238511e 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
@@ -16,16 +16,13 @@
 #include <functional>
 
 #include <cassert>
+#include <concepts>
 #include <tuple>
 #include <utility>
 
 #include "callable_types.h"
 #include "types.h"
 
-template <class Fn, class... Args>
-concept back_bindable =
-    requires(Fn&& fn, Args&&... args) { std::bind_back(std::forward<Fn>(fn), std::forward<Args>(args)...); };
-
 constexpr void test_basic_bindings() {
   { // Bind arguments, call without arguments
     {

>From 66632baa67282869fd0de13dd0144ed8a8df7be1 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 13 Mar 2024 01:35:31 +0100
Subject: [PATCH 3/6] Move `pass` to `bind_back.verify.cpp` and add better
 comment

Addresses https://github.com/llvm/llvm-project/pull/81055#discussion_r1521919109
---
 .../func.bind.partial/bind_back.verify.cpp             | 10 +++++++---
 .../function.objects/func.bind.partial/types.h         |  2 --
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
index f0841d39ca319f..bcb90b73372942 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
@@ -17,11 +17,15 @@
 
 #include "types.h"
 
+constexpr int pass(int n) { return n; }
+
 void test() {
-  { // Various failures
-    auto p = std::bind_back(pass, 1);
-    static_assert(p() == 1); // expected-error {{static assertion expression is not an integral constant expression}}
+  { // Test calling constexpr function from non-constexpr `bind_back` unspecified-type
+    auto f1 = std::bind_back(pass, 1);
+    static_assert(f1() == 1); // expected-error {{static assertion expression is not an integral constant expression}}
+  }
 
+  { // Various failures
     auto d = std::bind_back(do_nothing, 2); // expected-error {{no matching function for call to 'bind_back'}}
   }
 
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h b/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h
index 04e2061f7e78b8..76ed4d478baac1 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/types.h
@@ -35,8 +35,6 @@ struct CopyMoveInfo {
   constexpr CopyMoveInfo(CopyMoveInfo&&) : copy_kind(move) {}
 };
 
-constexpr int pass(int n) { return n; }
-
 template <class T>
 T do_nothing(T t) {
   return t;

>From 730b4e72184ce9a1914300b86e8748ab960c5122 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 13 Mar 2024 01:56:27 +0100
Subject: [PATCH 4/6] Test returning reference from the function (`bind_back`)

Addresses https://github.com/llvm/llvm-project/pull/81055#discussion_r1521924160
---
 .../func.bind.partial/bind_back.pass.cpp      | 27 ++++++++++++++++---
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
index 9c604c6238511e..01a96348d50c50 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.pass.cpp
@@ -92,10 +92,13 @@ constexpr void test_basic_bindings() {
   }
 
   { // Basic tests with fundamental types
-    int n      = 2;
-    int m      = 1;
-    auto add   = [](int x, int y) { return x + y; };
-    auto add_n = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+    int n         = 2;
+    int m         = 1;
+    int sum       = 0;
+    auto add      = [](int x, int y) { return x + y; };
+    auto add_n    = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+    auto add_ref  = [&](int x, int y) -> int& { return sum = x + y; };
+    auto add_rref = [&](int x, int y) -> int&& { return std::move(sum = x + y); };
 
     auto a = std::bind_back(add, m, n);
     assert(a() == 3);
@@ -106,6 +109,14 @@ constexpr void test_basic_bindings() {
     auto c = std::bind_back(add_n, n, m);
     assert(c(1, 1, 1, 1) == 7);
 
+    auto d = std::bind_back(add_ref, n, m);
+    std::same_as<int&> decltype(auto) dresult(d());
+    assert(dresult == 3);
+
+    auto e = std::bind_back(add_rref, n, m);
+    std::same_as<int&&> decltype(auto) eresult(e());
+    assert(eresult == 3);
+
     auto f = std::bind_back(add, n);
     assert(f(3) == 5);
 
@@ -114,6 +125,14 @@ constexpr void test_basic_bindings() {
 
     auto h = std::bind_back(add_n, 1, 1, 1);
     assert(h(2, 2, 2) == 9);
+
+    auto i = std::bind_back(add_ref, n);
+    std::same_as<int&> decltype(auto) iresult(i(5));
+    assert(iresult == 7);
+
+    auto j = std::bind_back(add_rref, m);
+    std::same_as<int&&> decltype(auto) jresult(j(4));
+    assert(jresult == 5);
   }
 }
 

>From 50d40239e22a7d021a982c70b50434ed128af18f Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 13 Mar 2024 02:09:57 +0100
Subject: [PATCH 5/6] Explain passing template function as an argument

Addresses (maybe) https://github.com/llvm/llvm-project/pull/81055#discussion_r1521918117
---
 .../function.objects/func.bind.partial/bind_back.verify.cpp  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
index bcb90b73372942..daf7aa27118559 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_back.verify.cpp
@@ -25,8 +25,9 @@ void test() {
     static_assert(f1() == 1); // expected-error {{static assertion expression is not an integral constant expression}}
   }
 
-  { // Various failures
-    auto d = std::bind_back(do_nothing, 2); // expected-error {{no matching function for call to 'bind_back'}}
+  { // Test calling `bind_back` with template function
+    auto f1 = std::bind_back(do_nothing, 2);
+    // expected-error at -1 {{no matching function for call to 'bind_back'}}
   }
 
   { // Mandates: is_constructible_v<decay_t<F>, F>

>From 88013f8f222dfdd111b950d5c4496616e03f3aa4 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Thu, 14 Mar 2024 13:47:49 +0100
Subject: [PATCH 6/6] Test returning reference from the function (`bind_front`)

Addresses https://github.com/llvm/llvm-project/pull/81055#discussion_r1521924160
---
 .../func.bind_front/bind_front.pass.cpp       | 29 +++++++++++++++----
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
index 6eb4e4a46e82f2..24e2f8522b5e56 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
@@ -142,12 +142,13 @@ constexpr bool test() {
 
   // Basic tests with fundamental types
   {
-    int n = 2;
-    int m = 1;
-    auto add = [](int x, int y) { return x + y; };
-    auto addN = [](int a, int b, int c, int d, int e, int f) {
-      return a + b + c + d + e + f;
-    };
+    int n         = 2;
+    int m         = 1;
+    int sum       = 0;
+    auto add      = [](int x, int y) { return x + y; };
+    auto addN     = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+    auto add_ref  = [&](int x, int y) -> int& { return sum = x + y; };
+    auto add_rref = [&](int x, int y) -> int&& { return std::move(sum = x + y); };
 
     auto a = std::bind_front(add, m, n);
     assert(a() == 3);
@@ -158,6 +159,14 @@ constexpr bool test() {
     auto c = std::bind_front(addN, n, m);
     assert(c(1, 1, 1, 1) == 7);
 
+    auto d = std::bind_back(add_ref, n, m);
+    std::same_as<int&> decltype(auto) dresult(d());
+    assert(dresult == 3);
+
+    auto e = std::bind_back(add_rref, n, m);
+    std::same_as<int&&> decltype(auto) eresult(e());
+    assert(eresult == 3);
+
     auto f = std::bind_front(add, n);
     assert(f(3) == 5);
 
@@ -166,6 +175,14 @@ constexpr bool test() {
 
     auto h = std::bind_front(addN, 1, 1, 1);
     assert(h(2, 2, 2) == 9);
+
+    auto i = std::bind_back(add_ref, n);
+    std::same_as<int&> decltype(auto) iresult(i(5));
+    assert(iresult == 7);
+
+    auto j = std::bind_back(add_rref, m);
+    std::same_as<int&&> decltype(auto) jresult(j(4));
+    assert(jresult == 5);
   }
 
   // Make sure we don't treat std::reference_wrapper specially.



More information about the libcxx-commits mailing list