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 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