-
Notifications
You must be signed in to change notification settings - Fork 15k
[libc++][functional] Implement std::bind_front<NTTP>
#165096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@llvm/pr-subscribers-libcxx Author: Jakub Mazurkiewicz (JMazurkiewicz) ChangesAdd Previous PR ( Full diff: https://github.com/llvm/llvm-project/pull/165096.diff 6 Files Affected:
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
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
|
|
|
||
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to use explicit this parameters.
| static_assert(_Fn != nullptr, "f cannot be equal to nullptr"); | ||
| } | ||
|
|
||
| return __nttp_bind_front_t<_Fn, decay_t<_Args>...>(std::forward<_Args>(__args)...); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When sizeof...(_Args) == 0, it seems better to return a functor with static operator().
| @@ -0,0 +1,64 @@ | |||
| //===----------------------------------------------------------------------===// | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No change requested for this file. The test failure said
2025-10-25T13:32:25.4922439Z # .---command stderr------------
2025-10-25T13:32:25.4923105Z # | error: 'expected-error' diagnostics seen but not expected:
2025-10-25T13:32:25.4924660Z # | File /home/gha/actions-runner/_work/llvm-project/llvm-project/build/generic-cxx26/libcxx/test-suite-install/include/c++/v1/__functional/bind_front.h Line 73: call to deleted constructor of 'Arg'
2025-10-25T13:32:25.4926047Z # | 1 error generated.
2025-10-25T13:32:25.4926371Z # `-----------------------------
But I think we can get rid of deleted overloads if we use explicit this parameters.
Add
std::bind_front<NTTP>function from P2714R1.Previous PR (
std::not_fn<NTTP>): #86133. Towards #105388.