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

Jakub Mazurkiewicz via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 28 05:17:17 PDT 2025


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

>From 44e4880b9a50ba5c6f092ae6874783b8b251478c Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 15 Oct 2025 13:25:38 +0200
Subject: [PATCH 1/6] [libc++][functional] Implement `bind_front<NTTP>`

---
 libcxx/include/__functional/bind_front.h | 34 ++++++++++++++++++++++++
 libcxx/include/functional                |  2 ++
 2 files changed, 36 insertions(+)

diff --git a/libcxx/include/__functional/bind_front.h b/libcxx/include/__functional/bind_front.h
index 87ef3affe80b6..1813209720cf9 100644
--- a/libcxx/include/__functional/bind_front.h
+++ b/libcxx/include/__functional/bind_front.h
@@ -17,6 +17,8 @@
 #include <__type_traits/decay.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/is_constructible.h>
+#include <__type_traits/is_member_pointer.h>
+#include <__type_traits/is_pointer.h>
 #include <__utility/forward.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -49,6 +51,38 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Fn&& __f, _Args&&... __args) {
 
 #endif // _LIBCPP_STD_VER >= 20
 
+#if _LIBCPP_STD_VER >= 26
+
+template <auto _Fn>
+struct __nttp_bind_front_op {
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const
+      noexcept(noexcept(std::invoke(_Fn, std::forward<_Args>(__args)...)))
+          -> decltype(std::invoke(_Fn, std::forward<_Args>(__args)...)) {
+    return std::invoke(_Fn, std::forward<_Args>(__args)...);
+  }
+};
+
+template <auto _Fn, class... _BoundArgs>
+struct __nttp_bind_front_t : __perfect_forward<__nttp_bind_front_op<_Fn>, _BoundArgs...> {
+  using __perfect_forward<__nttp_bind_front_op<_Fn>, _BoundArgs...>::__perfect_forward;
+};
+
+template <auto _Fn, class... _Args>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) {
+  static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...),
+                "bind_front requires all decay_t<Args> to be constructible from respective Args");
+  static_assert((is_move_constructible_v<decay_t<_Args>> && ...),
+                "bind_front requires all decay_t<Args> to be move constructible");
+  if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>) {
+    static_assert(_Fn != nullptr, "f cannot be equal to nullptr");
+  }
+
+  return __nttp_bind_front_t<_Fn, decay_t<_Args>...>(std::forward<_Args>(__args)...);
+}
+
+#endif // _LIBCPP_STD_VER >= 26
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___FUNCTIONAL_BIND_FRONT_H
diff --git a/libcxx/include/functional b/libcxx/include/functional
index 9ebcd818ec840..020754a96432c 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -221,6 +221,8 @@ template <auto f>
 // [func.bind.partial], function templates bind_front and bind_back
 template<class F, class... Args>
   constexpr unspecified bind_front(F&&, Args&&...); // C++20
+template<auto f, class... Args>
+  constexpr unspecified bind_front(Args&&...);      // C++26
 template<class F, class... Args>
   constexpr unspecified bind_back(F&&, Args&&...);  // C++23
 

>From 0a0f2e2e29529ed2a2e75b5384cb7198732358f4 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Wed, 15 Oct 2025 13:48:37 +0200
Subject: [PATCH 2/6] [libc++][functional] Test `bind_front<NTTP>`

---
 .../bind_front.nttp.nodiscard.verify.cpp      |  19 +
 .../bind_front.nttp.pass.cpp                  | 355 ++++++++++++++++++
 .../bind_front.nttp.verify.cpp                |  64 ++++
 .../func.bind.partial/types.h                 |   5 +
 4 files changed, 443 insertions(+)
 create mode 100644 libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.nodiscard.verify.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp

diff --git a/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.nodiscard.verify.cpp b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..3fcee5b9a2bad
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.nodiscard.verify.cpp
@@ -0,0 +1,19 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// Test the libc++ extension that std::bind_front<NTTP> is marked as [[nodiscard]].
+
+#include <functional>
+
+void test() {
+  std::bind_front<test>(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
new file mode 100644
index 0000000000000..12c751618bea9
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
@@ -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
+    auto f = [](int& val) {
+      val = 5;
+      return 10;
+    };
+
+    auto bf = std::bind_front<f>();
+    int val = 0;
+    assert(bf(val) == 10);
+    assert(val == 5);
+
+    using BF = decltype(bf);
+    static_assert(std::invocable<BF, int&>);
+    static_assert(!std::invocable<BF, int>);
+  }
+}
+
+constexpr void test_return_type() {
+  { // Test constructors and assignment operators
+    struct LeftShift {
+      constexpr unsigned int operator()(unsigned int x, unsigned int y) const { return x << y; }
+    };
+
+    auto power_of_2 = std::bind_front<LeftShift{}>(1);
+    assert(power_of_2(5) == 32U);
+    assert(power_of_2(4) == 16U);
+
+    auto moved = std::move(power_of_2);
+    assert(moved(6) == 64);
+    assert(moved(7) == 128);
+
+    auto copied = power_of_2;
+    assert(copied(3) == 8);
+    assert(copied(2) == 4);
+
+    moved = std::move(copied);
+    assert(copied(1) == 2);
+    assert(copied(0) == 1);
+
+    copied = moved;
+    assert(copied(8) == 256);
+    assert(copied(9) == 512);
+  }
+
+  { // Make sure `bind_front<NTTP>` unspecified-type's operator() is SFINAE-friendly.
+    using F = decltype(std::bind_front<[](int x, int y) { return x / y; }>(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);
+  }
+
+  { // Test noexceptness
+    auto always_noexcept = std::bind_front<MaybeNoexceptFn<true>{}>();
+    static_assert(noexcept(always_noexcept()));
+
+    auto never_noexcept = std::bind_front<MaybeNoexceptFn<false>{}>();
+    static_assert(!noexcept(never_noexcept()));
+  }
+
+  { // Test calling volatile wrapper
+    using Fn = decltype(std::bind_front<std::integral_constant<int, 0>{}>());
+    static_assert(!std::invocable<volatile Fn&>);
+    static_assert(!std::invocable<const volatile Fn&>);
+    static_assert(!std::invocable<volatile Fn>);
+    static_assert(!std::invocable<const volatile Fn>);
+  }
+}
+
+constexpr bool test() {
+  test_basic_bindings();
+  test_edge_cases();
+  test_passing_arguments();
+  test_perfect_forwarding_call_wrapper();
+  test_return_type();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert((test(), true));
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp
new file mode 100644
index 0000000000000..9bdf7797aafeb
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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&&...);
+// Mandates:
+// - (is_constructible_v<BoundArgs, Args> && ...) is true, and
+// - (is_move_constructible_v<BoundArgs> && ...) is true, and
+// - If is_pointer_v<F> || is_member_pointer_v<F> is true, then f != nullptr is true.
+
+#include <functional>
+
+struct AnyArgs {
+  template <class... Args>
+  void operator()(Args&&...) {}
+};
+
+void test() {
+  { // (is_constructible_v<BoundArgs, Args> && ...) is true
+    struct Arg {
+      Arg()           = default;
+      Arg(const Arg&) = default;
+      Arg(Arg&)       = delete;
+    };
+
+    Arg arg;
+    auto _ = std::bind_front<AnyArgs{}>(arg);
+    // expected-error@*:* {{static assertion failed due to requirement 'is_constructible_v<Arg, Arg &>': bind_front requires all decay_t<Args> to be constructible from respective Args}}
+  }
+
+  { // (is_move_constructible_v<BoundArgs> && ...) is true
+    struct Arg {
+      Arg()           = default;
+      Arg(Arg&&)      = delete;
+      Arg(const Arg&) = default;
+    };
+
+    Arg arg;
+    auto _ = std::bind_front<AnyArgs{}>(arg);
+    // expected-error@*:* {{static assertion failed due to requirement 'is_move_constructible_v<Arg>': bind_front requires all decay_t<Args> to be move constructible}}
+  }
+
+  { // If is_pointer_v<F> || is_member_pointer_v<F> is true, then f != nullptr is true
+    struct X {};
+
+    auto _ = std::bind_front<static_cast<void (*)()>(nullptr)>();
+    // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+
+    auto _ = std::bind_front<static_cast<int X::*>(nullptr)>();
+    // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+
+    auto _ = std::bind_front<static_cast<void (X::*)()>(nullptr)>();
+    // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+  }
+}
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 76ed4d478baac..98277e6231895 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
@@ -40,4 +40,9 @@ T do_nothing(T t) {
   return t;
 }
 
+template <bool IsNoexcept>
+struct MaybeNoexceptFn {
+  bool operator()() const noexcept(IsNoexcept); // not defined
+};
+
 #endif // TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H

>From 6d965f9da43d2d9bc82b4e5d552cb0b8f9a7f395 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Mon, 27 Oct 2025 14:18:27 +0100
Subject: [PATCH 3/6] Remove unnecessary `{}`

---
 libcxx/include/__functional/bind_front.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libcxx/include/__functional/bind_front.h b/libcxx/include/__functional/bind_front.h
index 1813209720cf9..e50a5c9b0d640 100644
--- a/libcxx/include/__functional/bind_front.h
+++ b/libcxx/include/__functional/bind_front.h
@@ -74,9 +74,8 @@ template <auto _Fn, class... _Args>
                 "bind_front requires all decay_t<Args> to be constructible from respective Args");
   static_assert((is_move_constructible_v<decay_t<_Args>> && ...),
                 "bind_front requires all decay_t<Args> to be move constructible");
-  if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>) {
+  if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>)
     static_assert(_Fn != nullptr, "f cannot be equal to nullptr");
-  }
 
   return __nttp_bind_front_t<_Fn, decay_t<_Args>...>(std::forward<_Args>(__args)...);
 }

>From cb4521533c711359b4de56b1c36efc545043f288 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Mon, 27 Oct 2025 19:48:20 +0100
Subject: [PATCH 4/6] Fix `bind_front.nttp.verify.cpp` test

---
 .../func.bind.partial/bind_front.nttp.verify.cpp                 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp
index 9bdf7797aafeb..e18074cbd4744 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.verify.cpp
@@ -35,6 +35,7 @@ void test() {
     Arg arg;
     auto _ = std::bind_front<AnyArgs{}>(arg);
     // expected-error@*:* {{static assertion failed due to requirement 'is_constructible_v<Arg, Arg &>': bind_front requires all decay_t<Args> to be constructible from respective Args}}
+    // expected-error@*:* 0-1{{call to deleted constructor of 'Arg'}}
   }
 
   { // (is_move_constructible_v<BoundArgs> && ...) is true

>From 4cfab17a9a2b7a6ace01d63a8823c9d79f8cb302 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Mon, 27 Oct 2025 21:10:17 +0100
Subject: [PATCH 5/6] Use explicit `this` parameter instead of
 `__perfect_forward` base class

---
 libcxx/include/__functional/bind_front.h      | 29 ++++++++++---------
 .../bind_front.nttp.pass.cpp                  | 10 +++----
 2 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/libcxx/include/__functional/bind_front.h b/libcxx/include/__functional/bind_front.h
index e50a5c9b0d640..43bdf39dc363d 100644
--- a/libcxx/include/__functional/bind_front.h
+++ b/libcxx/include/__functional/bind_front.h
@@ -53,21 +53,23 @@ _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Fn&& __f, _Args&&... __args) {
 
 #if _LIBCPP_STD_VER >= 26
 
-template <auto _Fn>
-struct __nttp_bind_front_op {
-  template <class... _Args>
-  _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const
-      noexcept(noexcept(std::invoke(_Fn, std::forward<_Args>(__args)...)))
-          -> decltype(std::invoke(_Fn, std::forward<_Args>(__args)...)) {
-    return std::invoke(_Fn, std::forward<_Args>(__args)...);
+template <auto _Fn, class _Indices, class... _BoundArgs>
+struct __nttp_bind_front_t;
+
+template <auto _Fn, size_t... _Indices, class... _BoundArgs>
+struct __nttp_bind_front_t<_Fn, index_sequence<_Indices...>, _BoundArgs...> {
+  tuple<_BoundArgs...> __bound_args_;
+
+  template <class _Self, class... _Args>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(this _Self&& __self, _Args&&... __args) noexcept(noexcept(std::invoke(
+      _Fn, std::get<_Indices>(std::forward<_Self>(__self).__bound_args_)..., std::forward<_Args>(__args)...)))
+      -> decltype(std::invoke(
+          _Fn, std::get<_Indices>(std::forward<_Self>(__self).__bound_args_)..., std::forward<_Args>(__args)...)) {
+    return std::invoke(
+        _Fn, std::get<_Indices>(std::forward<_Self>(__self).__bound_args_)..., std::forward<_Args>(__args)...);
   }
 };
 
-template <auto _Fn, class... _BoundArgs>
-struct __nttp_bind_front_t : __perfect_forward<__nttp_bind_front_op<_Fn>, _BoundArgs...> {
-  using __perfect_forward<__nttp_bind_front_op<_Fn>, _BoundArgs...>::__perfect_forward;
-};
-
 template <auto _Fn, class... _Args>
 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) {
   static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...),
@@ -77,7 +79,8 @@ template <auto _Fn, class... _Args>
   if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>)
     static_assert(_Fn != nullptr, "f cannot be equal to nullptr");
 
-  return __nttp_bind_front_t<_Fn, decay_t<_Args>...>(std::forward<_Args>(__args)...);
+  return __nttp_bind_front_t<_Fn, index_sequence_for<_Args...>, decay_t<_Args>...>{
+      .__bound_args_{std::forward<_Args>(__args)...}};
 }
 
 #endif // _LIBCPP_STD_VER >= 26
diff --git a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
index 12c751618bea9..db44dac21ca40 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind.partial/bind_front.nttp.pass.cpp
@@ -328,12 +328,12 @@ constexpr void test_return_type() {
     static_assert(!noexcept(never_noexcept()));
   }
 
-  { // Test calling volatile wrapper
+  { // Test calling volatile wrapper -- we allow it as an extension
     using Fn = decltype(std::bind_front<std::integral_constant<int, 0>{}>());
-    static_assert(!std::invocable<volatile Fn&>);
-    static_assert(!std::invocable<const volatile Fn&>);
-    static_assert(!std::invocable<volatile Fn>);
-    static_assert(!std::invocable<const volatile Fn>);
+    static_assert(std::invocable<volatile Fn&>);
+    static_assert(std::invocable<const volatile Fn&>);
+    static_assert(std::invocable<volatile Fn>);
+    static_assert(std::invocable<const volatile Fn>);
   }
 }
 

>From 328e8082b7e292916b10ffafc51251b434198dba Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Mon, 27 Oct 2025 21:59:31 +0100
Subject: [PATCH 6/6] Return different* function object when
 `sizeof...(_BoundArgs) == 0`

* Such that `std::is_empty_v<F>` is `true` and `operator()` is static.
---
 libcxx/include/__functional/bind_front.h      | 17 +++++++-
 .../bind_front.nttp.compile.pass.cpp          | 43 +++++++++++++++++++
 2 files changed, 58 insertions(+), 2 deletions(-)
 create mode 100644 libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.compile.pass.cpp

diff --git a/libcxx/include/__functional/bind_front.h b/libcxx/include/__functional/bind_front.h
index 43bdf39dc363d..9d2e561a28b6e 100644
--- a/libcxx/include/__functional/bind_front.h
+++ b/libcxx/include/__functional/bind_front.h
@@ -70,6 +70,16 @@ struct __nttp_bind_front_t<_Fn, index_sequence<_Indices...>, _BoundArgs...> {
   }
 };
 
+template <auto _Fn>
+struct __nttp_bind_without_bound_args_t {
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto
+  operator()(_Args&&... __args) noexcept(noexcept(std::invoke(_Fn, std::forward<_Args>(__args)...)))
+      -> decltype(std::invoke(_Fn, std::forward<_Args>(__args)...)) {
+    return std::invoke(_Fn, std::forward<_Args>(__args)...);
+  }
+};
+
 template <auto _Fn, class... _Args>
 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) {
   static_assert((is_constructible_v<decay_t<_Args>, _Args> && ...),
@@ -79,8 +89,11 @@ template <auto _Fn, class... _Args>
   if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>)
     static_assert(_Fn != nullptr, "f cannot be equal to nullptr");
 
-  return __nttp_bind_front_t<_Fn, index_sequence_for<_Args...>, decay_t<_Args>...>{
-      .__bound_args_{std::forward<_Args>(__args)...}};
+  if constexpr (sizeof...(_Args) == 0)
+    return __nttp_bind_without_bound_args_t<_Fn>{};
+  else
+    return __nttp_bind_front_t<_Fn, index_sequence_for<_Args...>, decay_t<_Args>...>{
+        .__bound_args_{std::forward<_Args>(__args)...}};
 }
 
 #endif // _LIBCPP_STD_VER >= 26
diff --git a/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.compile.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.compile.pass.cpp
new file mode 100644
index 0000000000000..f46cc7908862e
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_front.nttp.compile.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// Type of `std::bind_front<NTTP>(/* no bound args */)` is empty.
+
+#include <functional>
+#include <type_traits>
+
+struct NonEmptyFunctionObject {
+  int val = true;
+  void operator()() const;
+};
+
+void func();
+
+struct SomeClass {
+  long member_object;
+  void member_function();
+};
+
+using ResultWithEmptyFuncObject = decltype(std::bind_front<std::integral_constant<int, 0>{}>());
+static_assert(std::is_empty_v<ResultWithEmptyFuncObject>);
+
+using ResultWithNotEmptyFuncObject = decltype(std::bind_front<NonEmptyFunctionObject{}>());
+static_assert(std::is_empty_v<ResultWithNotEmptyFuncObject>);
+
+using ResultWithFunctionPointer = decltype(std::bind_front<func>());
+static_assert(std::is_empty_v<ResultWithFunctionPointer>);
+
+using ResultWithMemberObjectPointer = decltype(std::bind_front<&SomeClass::member_object>());
+static_assert(std::is_empty_v<ResultWithMemberObjectPointer>);
+
+using ResultWithMemberFunctionPointer = decltype(std::bind_front<&SomeClass::member_function>());
+static_assert(std::is_empty_v<ResultWithMemberFunctionPointer>);



More information about the libcxx-commits mailing list