From 44e4880b9a50ba5c6f092ae6874783b8b251478c Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Wed, 15 Oct 2025 13:25:38 +0200 Subject: [PATCH 1/6] [libc++][functional] Implement `bind_front` --- 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 +struct __nttp_bind_front_op { + template + _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 +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 +[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) { + static_assert((is_constructible_v, _Args> && ...), + "bind_front requires all decay_t to be constructible from respective Args"); + static_assert((is_move_constructible_v> && ...), + "bind_front requires all decay_t 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 // [func.bind.partial], function templates bind_front and bind_back template constexpr unspecified bind_front(F&&, Args&&...); // C++20 +template + constexpr unspecified bind_front(Args&&...); // C++26 template constexpr unspecified bind_back(F&&, Args&&...); // C++23 From 0a0f2e2e29529ed2a2e75b5384cb7198732358f4 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Wed, 15 Oct 2025 13:48:37 +0200 Subject: [PATCH 2/6] [libc++][functional] Test `bind_front` --- .../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 + +// + +// Test the libc++ extension that std::bind_front is marked as [[nodiscard]]. + +#include + +void test() { + std::bind_front(); // 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 + +// + +// template +// constexpr unspecified bind_front(Args&&...); + +#include + +#include +#include +#include +#include +#include + +#include "types.h" + +constexpr void test_basic_bindings() { + { // Bind arguments, call without arguments + { + auto f = std::bind_front(); + assert(f() == std::make_tuple()); + } + { + auto f = std::bind_front(Elem<1>{}); + assert(f() == std::make_tuple(Elem<1>{})); + } + { + auto f = std::bind_front(Elem<1>{}, Elem<2>{}); + assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{})); + } + { + auto f = std::bind_front(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(); + assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{})); + } + { + auto f = std::bind_front(); + assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{})); + } + { + auto f = std::bind_front(); + 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(Elem<1>{}); + assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<10>{})); + } + { + auto f = std::bind_front(Elem<1>{}, Elem<2>{}); + assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{})); + } + { + auto f = std::bind_front(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(Elem<1>{}); + assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}, Elem<11>{})); + } + { + auto f = std::bind_front(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(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(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(m, n); + assert(a() == 3); + + auto b = std::bind_front(m, n, m, m, m, m); + assert(b() == 7); + + auto c = std::bind_front(n, m); + assert(c(1, 1, 1, 1) == 7); + + auto f = std::bind_front(n); + assert(f(3) == 5); + + auto g = std::bind_front(n, 1); + assert(g() == 3); + + auto h = std::bind_front(1, 1, 1); + assert(h(2, 2, 2) == 9); + + auto i = std::bind_front(); + assert(i(o) == 1); + assert(o == 1); + + auto j = std::bind_front(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 a, std::reference_wrapper b) { return a.get() - b.get(); }; + + int i = 1; + int j = 2; + auto f = std::bind_front(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(info); + assert(f()); + } + + { + auto was_moved = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::move; }; + CopyMoveInfo info; + auto f = std::bind_front(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 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(); + using F = decltype(f); + assert(static_cast(f)() == 2); + assert(static_cast(f)() == 2); + assert(static_cast(f)() == 2); + assert(static_cast(f)() == 2); + } + + // Call to `bind_front` 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()); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + } + + { // 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()); + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + } + + { // 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()); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + } + + { // 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()); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + } + } + + { // Test perfect forwarding + auto f = [](int& val) { + val = 5; + return 10; + }; + + auto bf = std::bind_front(); + int val = 0; + assert(bf(val) == 10); + assert(val == 5); + + using BF = decltype(bf); + static_assert(std::invocable); + static_assert(!std::invocable); + } +} + +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(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` 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::value); + static_assert(std::is_invocable::value); + static_assert(!std::is_invocable::value); + static_assert(!std::is_invocable::value); + } + + { // Test noexceptness + auto always_noexcept = std::bind_front{}>(); + static_assert(noexcept(always_noexcept())); + + auto never_noexcept = std::bind_front{}>(); + static_assert(!noexcept(never_noexcept())); + } + + { // Test calling volatile wrapper + using Fn = decltype(std::bind_front{}>()); + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + } +} + +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 + +// + +// template +// constexpr unspecified bind_front(Args&&...); +// Mandates: +// - (is_constructible_v && ...) is true, and +// - (is_move_constructible_v && ...) is true, and +// - If is_pointer_v || is_member_pointer_v is true, then f != nullptr is true. + +#include + +struct AnyArgs { + template + void operator()(Args&&...) {} +}; + +void test() { + { // (is_constructible_v && ...) is true + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&) = delete; + }; + + Arg arg; + auto _ = std::bind_front(arg); + // expected-error@*:* {{static assertion failed due to requirement 'is_constructible_v': bind_front requires all decay_t to be constructible from respective Args}} + } + + { // (is_move_constructible_v && ...) is true + struct Arg { + Arg() = default; + Arg(Arg&&) = delete; + Arg(const Arg&) = default; + }; + + Arg arg; + auto _ = std::bind_front(arg); + // expected-error@*:* {{static assertion failed due to requirement 'is_move_constructible_v': bind_front requires all decay_t to be move constructible}} + } + + { // If is_pointer_v || is_member_pointer_v is true, then f != nullptr is true + struct X {}; + + auto _ = std::bind_front(nullptr)>(); + // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}} + + auto _ = std::bind_front(nullptr)>(); + // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}} + + auto _ = std::bind_front(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 +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 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 "bind_front requires all decay_t to be constructible from respective Args"); static_assert((is_move_constructible_v> && ...), "bind_front requires all decay_t 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 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(arg); // expected-error@*:* {{static assertion failed due to requirement 'is_constructible_v': bind_front requires all decay_t to be constructible from respective Args}} + // expected-error@*:* 0-1{{call to deleted constructor of 'Arg'}} } { // (is_move_constructible_v && ...) is true From 4cfab17a9a2b7a6ace01d63a8823c9d79f8cb302 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz 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 -struct __nttp_bind_front_op { - template - _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 +struct __nttp_bind_front_t; + +template +struct __nttp_bind_front_t<_Fn, index_sequence<_Indices...>, _BoundArgs...> { + tuple<_BoundArgs...> __bound_args_; + + template + _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 -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 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) { static_assert((is_constructible_v, _Args> && ...), @@ -77,7 +79,8 @@ template 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{}>()); - static_assert(!std::invocable); - static_assert(!std::invocable); - static_assert(!std::invocable); - static_assert(!std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); } } From 328e8082b7e292916b10ffafc51251b434198dba Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz 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` 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 +struct __nttp_bind_without_bound_args_t { + template + _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 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto bind_front(_Args&&... __args) { static_assert((is_constructible_v, _Args> && ...), @@ -79,8 +89,11 @@ template 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 + +// + +// Type of `std::bind_front(/* no bound args */)` is empty. + +#include +#include + +struct NonEmptyFunctionObject { + int val = true; + void operator()() const; +}; + +void func(); + +struct SomeClass { + long member_object; + void member_function(); +}; + +using ResultWithEmptyFuncObject = decltype(std::bind_front{}>()); +static_assert(std::is_empty_v); + +using ResultWithNotEmptyFuncObject = decltype(std::bind_front()); +static_assert(std::is_empty_v); + +using ResultWithFunctionPointer = decltype(std::bind_front()); +static_assert(std::is_empty_v); + +using ResultWithMemberObjectPointer = decltype(std::bind_front<&SomeClass::member_object>()); +static_assert(std::is_empty_v); + +using ResultWithMemberFunctionPointer = decltype(std::bind_front<&SomeClass::member_function>()); +static_assert(std::is_empty_v);