[libcxx-commits] [libcxx] [libc++][functional] Implement `std::bind_front<NTTP>` (PR #165096)
Jakub Mazurkiewicz via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Oct 25 06:18:58 PDT 2025
https://github.com/JMazurkiewicz created https://github.com/llvm/llvm-project/pull/165096
Add `std::bind_front<NTTP>` function from [P2714R1](https://wg21.link/p2714r1).
Previous PR (`std::not_fn<NTTP>`): https://github.com/llvm/llvm-project/pull/86133. Towards https://github.com/llvm/llvm-project/issues/105388.
>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/2] [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/2] [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
More information about the libcxx-commits
mailing list