[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