[libcxx-commits] [libcxx] [libc++][functional] Implement `std::bind_front<NTTP>` (PR #165096)

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Nov 2 01:44:38 PST 2025


================
@@ -0,0 +1,355 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <functional>
+
+// template<auto f, class... Args>
+//   constexpr unspecified bind_front(Args&&...);
+
+#include <functional>
+
+#include <cassert>
+#include <concepts>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "types.h"
+
+constexpr void test_basic_bindings() {
+  { // Bind arguments, call without arguments
+    {
+      auto f = std::bind_front<MakeTuple{}>();
+      assert(f() == std::make_tuple());
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{});
+      assert(f() == std::make_tuple(Elem<1>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+      assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_front<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_front<MakeTuple{}>();
+      assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>();
+      assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}));
+    }
+    {
+      auto f = std::bind_front<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_front<MakeTuple{}>(Elem<1>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}));
+    }
+
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}, Elem<11>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}, Elem<11>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}, Elem<11>{}));
+    }
+    {
+      auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+      assert(f(Elem<10>{}, Elem<11>{}, Elem<12>{}) ==
+             std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}, Elem<11>{}, Elem<12>{}));
+    }
+  }
+
+  { // Basic tests with fundamental types
+    const int n = 2;
+    const int m = 1;
+    int o       = 0;
+
+    auto add       = [](int x, int y) { return x + y; };
+    auto add6      = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+    auto increment = [](int& x) { return ++x; };
+
+    auto a = std::bind_front<add>(m, n);
+    assert(a() == 3);
+
+    auto b = std::bind_front<add6>(m, n, m, m, m, m);
+    assert(b() == 7);
+
+    auto c = std::bind_front<add6>(n, m);
+    assert(c(1, 1, 1, 1) == 7);
+
+    auto f = std::bind_front<add>(n);
+    assert(f(3) == 5);
+
+    auto g = std::bind_front<add>(n, 1);
+    assert(g() == 3);
+
+    auto h = std::bind_front<add6>(1, 1, 1);
+    assert(h(2, 2, 2) == 9);
+
+    auto i = std::bind_front<increment>();
+    assert(i(o) == 1);
+    assert(o == 1);
+
+    auto j = std::bind_front<increment>(std::ref(o));
+    assert(j() == 2);
+    assert(o == 2);
+  }
+}
+
+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_front<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 mul(int x, int y) { return x * y; }
+    };
+
+    MemberFunction value;
+    auto fn = std::bind_front<&MemberFunction::mul>(value, 2);
+    assert(fn(3) == 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 fn1 = std::bind_front<&MemberObject::obj>();
+    assert(fn1(value) == 3);
+    auto fn2 = std::bind_front<&MemberObject::obj>(value);
+    assert(fn2() == 3);
+  }
+}
+
+constexpr void test_passing_arguments() {
+  { // Make sure that we copy the bound arguments into the unspecified-type.
+    int n  = 2;
+    auto f = std::bind_front<[](int x, int y) { return x + y; }>(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_front<was_copied>(info);
+      assert(f());
+    }
+
+    {
+      auto was_moved = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::move; };
+      CopyMoveInfo info;
+      auto f = std::bind_front<was_moved>(info);
+      assert(std::move(f)());
+    }
+  }
+}
+
+constexpr void test_perfect_forwarding_call_wrapper() {
+  { // Make sure we call the correctly cv-ref qualified operator()
+    // based on the value category of the bind_front<NTTP> 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_front<X{}>();
+    using F = decltype(f);
+    assert(static_cast<F&>(f)() == 2);
+    assert(static_cast<const F&>(f)() == 2);
+    assert(static_cast<F&&>(f)() == 2);
+    assert(static_cast<const F&&>(f)() == 2);
+  }
+
+  // Call to `bind_front<NTTP>` unspecified-type's operator() should always result in call to the const& overload of the underlying function object.
+  {
+    { // Make sure unspecified-type is still callable when we delete the & overload.
+      struct X {
+        int operator()() & = delete;
+        int operator()() const&;
+        int operator()() &&;
+        int operator()() const&&;
+      };
+
+      using F = decltype(std::bind_front<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 unspecified-type is not callable when we delete the const& overload.
+      struct X {
+        int operator()() &;
+        int operator()() const& = delete;
+        int operator()() &&;
+        int operator()() const&&;
+      };
+
+      using F = decltype(std::bind_front<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 unspecified-type is still callable when we delete the && overload.
+      struct X {
+        int operator()() &;
+        int operator()() const&;
+        int operator()() && = delete;
+        int operator()() const&&;
+      };
+
+      using F = decltype(std::bind_front<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 unspecified-type is still callable when we delete the const&& overload.
+      struct X {
+        int operator()() &;
+        int operator()() const&;
+        int operator()() &&;
+        int operator()() const&& = delete;
+      };
+
+      using F = decltype(std::bind_front<X{}>());
+      static_assert(std::invocable<F&>);
+      static_assert(std::invocable<const F&>);
+      static_assert(std::invocable<F>);
+      static_assert(std::invocable<const F>);
+    }
+  }
+
+  { // Test perfect forwarding
----------------
huixie90 wrote:

I think what is missing and important is that the bond arguments are correct perfectly forward-like  as the returned object. given
```
constexpr struct F {
 
  int operator()(int&, double) const;
  int operator()(const int&, double) const;
  int operator()(int&&, double) const;
  int operator()(const int&&, double) const;
} f{};
```
and

```
auto r = bind_front<f>(5);
```

the results of
```
r(1.0);
std::as_const(r)(1.0);
std::move(r)(1.0);
std::move(std::as_const(r))(1.0);
```

should call different overloads

https://github.com/llvm/llvm-project/pull/165096


More information about the libcxx-commits mailing list