From a77146f01563b5df19e70061dc237178b5532aa9 Mon Sep 17 00:00:00 2001 From: Nathan Myers Date: Thu, 3 Jul 2025 17:15:40 -0400 Subject: [PATCH] libstdc++: Add NTTP bind_front, -back, not_fn (P2714) [PR119744] Add non-type template parameter function-object/-pointer argument versions of bind_front, bind_back, and not_fn. This introduces a new internal type _Bind_fn_t to carry the template-argument function object when no arguments are bound, used in both bind_front and bind_back. Otherwise they return a lambda object that has captured the arguments. There is no need to change libstdc++-v3/src/c++23/std.cc.in because existing exports: "using std::bind_front;" etc. export the new overloads. libstdc++-v3/ChangeLog: PR libstdc++/119744 * include/bits/version.def: Redefine __cpp_lib_bind_front etc. * include/bits/version.h: Ditto. * include/std/functional: Add new bind_front etc. overloads * testsuite/20_util/function_objects/bind_back/1.cc: Check plumbing better * testsuite/20_util/function_objects/bind_back/nttp.cc: New test. * testsuite/20_util/function_objects/bind_back/nttp_neg.cc: New test. * testsuite/20_util/function_objects/bind_front/1.cc: Check plumbing better * testsuite/20_util/function_objects/bind_front/nttp.cc: New test. * testsuite/20_util/function_objects/bind_front/nttp_neg.cc: New test. * testsuite/20_util/function_objects/not_fn/nttp.cc: New test. * testsuite/20_util/function_objects/not_fn/nttp_neg.cc: New test. * testsuite/20_util/headers/functional/synopsis.cc: New decls. --- libstdc++-v3/include/bits/version.def | 12 + libstdc++-v3/include/bits/version.h | 21 +- libstdc++-v3/include/std/functional | 142 +++++++++- .../20_util/function_objects/bind_back/1.cc | 16 +- .../function_objects/bind_back/nttp.cc | 258 +++++++++++++++++ .../function_objects/bind_back/nttp_neg.cc | 38 +++ .../20_util/function_objects/bind_front/1.cc | 16 +- .../function_objects/bind_front/nttp.cc | 260 ++++++++++++++++++ .../function_objects/bind_front/nttp_neg.cc | 38 +++ .../20_util/function_objects/not_fn/nttp.cc | 100 +++++++ .../function_objects/not_fn/nttp_neg.cc | 28 ++ .../20_util/headers/functional/synopsis.cc | 25 ++ 12 files changed, 932 insertions(+), 22 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp_neg.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp_neg.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp_neg.cc diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 65cd6ab80b8b..721125df280f 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -471,6 +471,10 @@ ftms = { ftms = { name = not_fn; + values = { + v = 202306; + cxxmin = 26; + }; values = { v = 201603; cxxmin = 17; @@ -802,6 +806,10 @@ ftms = { ftms = { name = bind_front; + values = { + v = 202306; + cxxmin = 26; + }; values = { v = 201907; cxxmin = 20; @@ -810,6 +818,10 @@ ftms = { ftms = { name = bind_back; + values = { + v = 202306; + cxxmin = 26; + }; values = { v = 202202; cxxmin = 23; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index af50ce4b362f..7e8ad1007efa 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -521,7 +521,12 @@ #undef __glibcxx_want_make_from_tuple #if !defined(__cpp_lib_not_fn) -# if (__cplusplus >= 201703L) +# if (__cplusplus > 202302L) +# define __glibcxx_not_fn 202306L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) +# define __cpp_lib_not_fn 202306L +# endif +# elif (__cplusplus >= 201703L) # define __glibcxx_not_fn 201603L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) # define __cpp_lib_not_fn 201603L @@ -895,7 +900,12 @@ #undef __glibcxx_want_atomic_value_initialization #if !defined(__cpp_lib_bind_front) -# if (__cplusplus >= 202002L) +# if (__cplusplus > 202302L) +# define __glibcxx_bind_front 202306L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) +# define __cpp_lib_bind_front 202306L +# endif +# elif (__cplusplus >= 202002L) # define __glibcxx_bind_front 201907L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) # define __cpp_lib_bind_front 201907L @@ -905,7 +915,12 @@ #undef __glibcxx_want_bind_front #if !defined(__cpp_lib_bind_back) -# if (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) +# if (__cplusplus > 202302L) +# define __glibcxx_bind_back 202306L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) +# define __cpp_lib_bind_back 202306L +# endif +# elif (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) # define __glibcxx_bind_back 202202L # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) # define __cpp_lib_bind_back 202202L diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional index f86030d4ebf0..8ad73b343bda 100644 --- a/libstdc++-v3/include/std/functional +++ b/libstdc++-v3/include/std/functional @@ -68,7 +68,6 @@ #include #include -#include #include #include #include // std::reference_wrapper and _Mem_fn_traits @@ -88,7 +87,8 @@ # include // std::identity, ranges::equal_to etc. # include #endif -#if __glibcxx_move_only_function || __glibcxx_copyable_function || __glibcxx_function_ref +#if __glibcxx_move_only_function || __glibcxx_copyable_function || \ + __glibcxx_function_ref # include #endif @@ -922,6 +922,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION std::forward<_BoundArgs>(__args)...); } +#if __cpp_lib_bind_front >= 202306L || __cpp_lib_bind_back >= 202306L + template + struct _Bind_fn_t + { + using _Fn = const decltype(__fn)&; + template + constexpr static decltype(auto) + operator()(_Args... __args) + noexcept(is_nothrow_invocable_v<_Fn, _Args...>) + requires is_invocable_v<_Fn, _Args...> + { return std::invoke(__fn, std::forward<_Args>(__args)...); } + }; +#endif + #ifdef __cpp_lib_bind_front // C++ >= 20 /** Create call wrapper by partial application of arguments to function. * @@ -941,6 +955,50 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return _Bind_front_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); } + +#if __cpp_lib_bind_front >= 202306L + + /** Create call wrapper by partial application of arguments to function. + * + * The result of `std::bind_front(bind_args...)` is a function object + * that stores the bound arguments, `bind_args...`. When that function + * object is invoked with `call_args...` it returns the result of calling + * `fn(bind_args..., call_args...)`. + * + * @since C++26 + */ + template + constexpr decltype(auto) + bind_front(_BindArgs&&... __bind_args) + noexcept(__and_v...>) + { + using _Fn = decltype(__fn); + static_assert( + (is_constructible_v, _BindArgs> && ...) && + (is_move_constructible_v> && ...)); + if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>) + static_assert(__fn != nullptr); + + if constexpr (sizeof...(_BindArgs) == 0) + return _Bind_fn_t<__fn>{}; + else { + return [... __bound_args(std::forward<_BindArgs>(__bind_args))] + + (this _Self&&, _CallArgs&&... __call_args) + noexcept(is_nothrow_invocable_v< + const _Fn&, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>) + -> decltype(auto) + requires is_invocable_v< + const _Fn&, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...> + { + return std::invoke(__fn, + std::forward_like<_Self>(__bound_args)..., + std::forward<_CallArgs>(__call_args)...); + }; + } + } + +#endif // __cpp_lib_bind_front // C++26 #endif // __cpp_lib_bind_front #ifdef __cpp_lib_bind_back // C++ >= 23 @@ -962,6 +1020,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return _Bind_back_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); } + +#if __cpp_lib_bind_back >= 202306L + + /** Create call wrapper by partial application of arguments to function. + * + * The result of `std::bind_back(bind_args...)` is a function object + * that stores the arguments, `bind_args...`. When that function object + * is invoked with `call_args...` it returns the result of calling + * `fn(call_args..., bind_args...)`. + * + * @since C++26 + */ + template + constexpr decltype(auto) + bind_back(_BindArgs&&... __bind_args) + noexcept(__and_v...>) + { + using _Fn = decltype(__fn); + static_assert( + (is_constructible_v, _BindArgs> && ...) && + (is_move_constructible_v> && ...)); + if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>) + static_assert(__fn != nullptr); + + if constexpr (sizeof...(_BindArgs) == 0) + return _Bind_fn_t<__fn>{}; + else + { + // Capture arguments in a lambda and return that. + return [... __bound_args(std::forward<_BindArgs>(__bind_args))] + + (this _Self&&, _CallArgs&&... __call_args) + noexcept(is_nothrow_invocable_v< + const _Fn&, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>) + -> decltype(auto) + requires is_invocable_v< + const _Fn&, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...> + { + return std::invoke(__fn, + std::forward<_CallArgs>(__call_args)..., + std::forward_like<_Self>(__bound_args)...); + }; + } + } + +#endif // __cpp_lib_bind_back // C++26, nttp #endif // __cpp_lib_bind_back #if __cplusplus >= 201402L @@ -1062,7 +1166,39 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return _Not_fn>{std::forward<_Fn>(__fn), 0}; } -#endif + +#if __cpp_lib_not_fn >= 202306L + + /** Wrap a function type to create a function object that negates its result. + * + * The function template `std::not_fn` creates a "forwarding call wrapper", + * which is a function object that when called forwards its arguments to + * its invocable template argument. + * + * The result of invoking the wrapper is the negation (using `!`) of + * the wrapped function object. + * + * @ingroup functors + * @since C++26 + */ + template + constexpr decltype(auto) + not_fn() noexcept + { + using _Fn = decltype(__fn); + if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>) + static_assert(__fn != nullptr); + return [](_Args... __args) static + noexcept(noexcept( + !std::invoke(__fn, std::forward<_Args>(__args)...) )) + -> decltype(auto) + requires requires { + !std::invoke(__fn, std::forward<_Args>(__args)...); } + { return !std::invoke(__fn, std::forward<_Args>(__args)...); }; + }; + +#endif // __cpp_lib_not_fn >= 202306L +#endif // __cpp_lib_not_fn #if __cplusplus >= 201703L // Searchers diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc index 7141be282c04..743b7b0efb2e 100644 --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc @@ -266,22 +266,22 @@ test03() static_assert(is_invocable_r_v); } -constexpr int f(int i, int j, int k) { return i + 2*(j + k); } +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } constexpr bool test04() { auto g = bind_back(f); - VERIFY( g(1, 2, 3) == 1 + 2*(2 + 3) ); + VERIFY( g(1, 2, 3) == 1 + 2*2 + 3*3 ); auto g1 = bind_back(f, 1); - VERIFY( g1(2, 3) == 2 + 2*(3 + 1) ); - VERIFY( bind_back(g, 1)(2, 3) == 2 + 2*(3 + 1) ); + VERIFY( g1(2, 3) == 3*1 + 2 + 3*2); + VERIFY( bind_back(g, 1)(2, 3) == 3*1 + 2 + 2*3 ); auto g2 = bind_back(f, 1, 2); - VERIFY( g2(3) == 3 + 2*(1 + 2) ); - VERIFY( bind_back(g1, 2)(3) == 3 + 2*(2 + 1) ); + VERIFY( g2(3) == 3 + 2*1 + 3*2); + VERIFY( bind_back(g1, 2)(3) == 3*1 + 2*2 + 3 ); auto g3 = bind_back(f, 1, 2, 3); - VERIFY( g3() == 1 + 2*(2 + 3) ); - VERIFY( bind_back(g2, 3)() == 3 + 2*(1 + 2) ); + VERIFY( g3() == 1 + 2*2 + 3*3 ); + VERIFY( bind_back(g2, 3)() == 3*1 + 1*2 + 2*3); return true; } diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc new file mode 100644 index 000000000000..4dff909a3873 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc @@ -0,0 +1,258 @@ +// { dg-do run { target c++26 } } +// { dg-add-options no_pch } + +// Test NTTP bind_back(Args...), P2714 + +#include + +#ifndef __cpp_lib_bind_back +# error "Feature test macro for bind_back is missing in " +#elif __cpp_lib_bind_back < 202306L +# error "Feature test macro for bind_back has wrong value in " +#endif + +#include + +using std::bind_back; +using std::is_same_v; +using std::is_invocable_v; +using std::is_invocable_r_v; + +void +test01() +{ + struct F { void operator()(int) {} }; + constexpr F f{}; + + // Reference wrappers should be handled: + static_assert(!std::is_same_v< + decltype(bind_back(std::declval())), + decltype(bind_back(std::ref(std::declval()))) + >); + static_assert(!std::is_same_v< + decltype(bind_back(std::declval())), + decltype(bind_back(std::cref(std::declval()))) + >); + static_assert(!std::is_same_v< + decltype(bind_back(std::ref(std::declval()))), + decltype(bind_back(std::cref(std::declval()))) + >); +} + +void +test02() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + + struct F + { + quals operator()(int, int) & { return { false, true }; } + quals operator()(int, int) const & { return { true, true }; } + quals operator()(int, int) && { return { false, false }; } + quals operator()(int, int) const && { return { true, false }; } + }; + + // Constness and value category forwarded to the target object? + { // no bound args + constexpr F f; + auto g = bind_back(); + const auto& cg = g; + quals q; + + q = g(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(0,0); + VERIFY( q.as_const && q.as_lvalue ); + } + { // one bound arg + constexpr F f; + auto g = bind_back(0); + const auto& cg = g; + quals q; + + q = g(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(0); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(0); + VERIFY( q.as_const && q.as_lvalue ); + } + { // two bound args, the general case + constexpr F f; + auto g = bind_back(0,0); + const auto& cg = g; + quals q; + + q = g(); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(); + VERIFY( q.as_const && q.as_lvalue ); + } +} + +void +test02a() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + struct F + { + quals operator()(int, int&) const { return { false, true }; } + quals operator()(int, int const&) const { return { true, true }; } + quals operator()(int, int&&) const { return { false, false }; } + quals operator()(int, int const&&) const { return { true, false }; } + }; + constexpr F f{}; + + // verify propagation + auto h = bind_back(10); + auto const& ch = h; + quals q; + + q = h(0); + VERIFY( !q.as_const && q.as_lvalue ); + q = ch(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(h)(0); + VERIFY( !q.as_const && !q.as_lvalue ); + q = std::move(ch)(0); + VERIFY( q.as_const && !q.as_lvalue ); +} + +void +test03() +{ + struct F + { + int& operator()(void*, int& i) { return i; } + void* operator()(void* p, int) const { return p; } + }; + + int i = 5; + void* vp = &vp; // arbitrary void* value + constexpr F f; + + // Bound arg always forwarded as const int& so can only call second overload: + + auto g0 = bind_back(); // call wrapper has no bound arg + using G0 = decltype(g0); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p0 = static_cast(g0)(vp, i); + VERIFY( p0 == vp ); + + auto g1 = bind_back(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + static_assert(!is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p1 = static_cast(g1)(vp); + VERIFY( p1 == vp ); + + auto g2 = bind_back(std::ref(i)); // bound arg of type int& + using G2 = decltype(g2); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p2 = g2(vp); + VERIFY( p2 == vp ); + p2 = static_cast(g2)(vp); + VERIFY( p2 == vp ); + p2 = const_cast(g2)(vp); + VERIFY( p2 == vp ); + + auto g3 = bind_back(std::cref(i)); // bound arg of type const int& + using G3 = decltype(g3); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); +} + +void test03a() +{ + struct F + { + int& operator()(void*, int& i) { return i; } + void* operator()(void* p, long) const { return p; } + }; + + int i = 5; + void* vp = &vp; // arbitrary void* value + constexpr F f; + + // Bound arg always forwarded as const int& so can only call second overload: + auto g1 = bind_back(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p1 = static_cast(g1)(vp); + VERIFY( p1 == vp ); +} + + +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } + +consteval bool +test04() +{ + constexpr auto g = bind_back(); + VERIFY( std::is_empty_v ); + VERIFY(g(1, 2, 3) == 1 + 2*2 + 3*3 ); + constexpr auto g1 = bind_back(1); + VERIFY(g1(2, 3) == 3*1 + 1*2 + 2*3 ); + VERIFY(bind_back(1)(2, 3) == 3*1 + 1*2 + 2*3 ); + constexpr auto g2 = bind_back(1, 2); + VERIFY(g2(3) == 2*1 + 3*2 + 1*3 ); + VERIFY(bind_back(2)(3) == 3*1 + 2*2 + 1*3 ); + constexpr auto g3 = bind_back(1, 2, 3); + VERIFY(g3() == 1 + 2*2 + 3*3); + VERIFY(bind_back(3)() == 1*2 + 2*3 + 3*1 ); + return true; +} + +struct C { int i = 0; }; +struct D : C { D(){} D(D&&) { ++i; } }; +int f5(D const& d1, D const& d2, D const& d3) +{ return d1.i + d2.i + d3.i; } + +void test05() +{ + // Must move arguments into capture object, not construct in place + // like normal arguments. + VERIFY( bind_back(D{}, D{})(D{}) == 2 ); +} + +int +main() +{ + test01(); + test02(); + test02a(); + test03(); + test03a(); + static_assert(test04()); + test05(); +} diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp_neg.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp_neg.cc new file mode 100644 index 000000000000..d64d49e83f4b --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp_neg.cc @@ -0,0 +1,38 @@ +// { dg-do compile { target c++26 } } + +#include + +void f() {}; +using fp = decltype(&f); +constexpr fp nfp = nullptr; + +struct A { void mf() const {} }; +using mfp = decltype(&A::mf); +constexpr mfp nnmfp = &A::mf; +constexpr mfp nmfp = nullptr; + +struct B { B() = default; B(B const&) = delete; }; +void bf(B const&) {}; + +struct C { C() = default; C(C&&) = delete; }; +void cf(C&&) {}; + +int main() +{ + std::bind_back()(); + // Verify bind_back with fn a null pointer fails. + std::bind_back()(); // { dg-error "here" } + + std::bind_back(A{})(); + // Verify bind_back with mfn a null member pointer fails. + std::bind_back(A{})(); // { dg-error "here" } + + // Verify passing uncopyable type fails. + std::bind_back(B{}); // { dg-error "here" } + // + // Verify passing unmovable type fails. + std::bind_back(C{}); // { dg-error "here" } +} + +// { dg-error "static assertion failed" "" { target *-*-* } 0 } +// { dg-error "use of deleted function" "" { target *-*-* } 0 } diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc index 93efb2efea40..56bdaaa46d0c 100644 --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc @@ -266,22 +266,22 @@ test03() static_assert(is_invocable_r_v); } -int f(int i, int j, int k) { return i + j + k; } +int f(int i, int j, int k) { return i + 2*j + 3*k; } void test04() { auto g = bind_front(f); - VERIFY( g(1, 2, 3) == 6 ); + VERIFY( g(1, 2, 3) == 14 ); auto g1 = bind_front(f, 1); - VERIFY( g1(2, 3) == 6 ); - VERIFY( bind_front(g, 1)(2, 3) == 6 ); + VERIFY( g1(2, 3) == 14 ); + VERIFY( bind_front(g, 1)(2, 3) == 14 ); auto g2 = bind_front(f, 1, 2); - VERIFY( g2(3) == 6 ); - VERIFY( bind_front(g1, 2)(3) == 6 ); + VERIFY( g2(3) == 14 ); + VERIFY( bind_front(g1, 2)(3) == 14 ); auto g3 = bind_front(f, 1, 2, 3); - VERIFY( g3() == 6 ); - VERIFY( bind_front(g2, 3)() == 6 ); + VERIFY( g3() == 14 ); + VERIFY( bind_front(g2, 3)() == 14 ); } struct CountedArg diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc new file mode 100644 index 000000000000..0839c849c0ce --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc @@ -0,0 +1,260 @@ +// { dg-do run { target c++26 } } +// { dg-add-options no_pch } + +// Test NTTP bind_front(Args...), P2714 + +#include + +#ifndef __cpp_lib_bind_front +# error "Feature test macro for bind_front is missing in " +#elif __cpp_lib_bind_front < 202306L +# error "Feature test macro for bind_front has wrong value in " +#endif + +#include + +using std::bind_front; +using std::is_same_v; +using std::is_invocable_v; +using std::is_invocable_r_v; + +void +test01() +{ + struct F { void operator()(int) {} }; + constexpr F f{}; + + // Reference wrappers should be handled: + static_assert(!std::is_same_v< + decltype(bind_front(std::declval())), + decltype(bind_front(std::ref(std::declval()))) + >); + static_assert(!std::is_same_v< + decltype(bind_front(std::declval())), + decltype(bind_front(std::cref(std::declval()))) + >); + static_assert(!std::is_same_v< + decltype(bind_front(std::ref(std::declval()))), + decltype(bind_front(std::cref(std::declval()))) + >); +} + +void +test02() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + + struct F + { + quals operator()(int, int) & { return { false, true }; } + quals operator()(int, int) const & { return { true, true }; } + quals operator()(int, int) && { return { false, false }; } + quals operator()(int, int) const && { return { true, false }; } + }; + + // Constness and value category forwarded to the target object? + { // no bound args + constexpr F f; + auto g = bind_front(); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? + q = g(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(0,0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(0,0); + VERIFY( q.as_const && q.as_lvalue ); + } + { // one bound arg (for when we implement that as a separate case) + constexpr F f; + auto g = bind_front(0); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? + q = g(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(0); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(0); + VERIFY( q.as_const && q.as_lvalue ); + } + { // two bound args, the general case + constexpr F f; + auto g = bind_front(0,0); + const auto& cg = g; + quals q; + + q = g(); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(g)(); + VERIFY( q.as_const && q.as_lvalue ); + q = cg(); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(cg)(); + VERIFY( q.as_const && q.as_lvalue ); + } +} + +void +test02a() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + + struct F + { + quals operator()(int&, int) const { return { false, true }; } + quals operator()(int const&, int) const { return { true, true }; } + quals operator()(int&&, int) const { return { false, false }; } + quals operator()(int const&&, int) const { return { true, false }; } + }; + constexpr F f{}; + + // verify propagation + auto h = bind_front(10); + auto const& ch = h; + quals q; + + q = h(0); + VERIFY( !q.as_const && q.as_lvalue ); + q = ch(0); + VERIFY( q.as_const && q.as_lvalue ); + q = std::move(h)(0); + VERIFY( !q.as_const && !q.as_lvalue ); + q = std::move(ch)(0); + VERIFY( q.as_const && !q.as_lvalue ); +} + +void +test03() +{ + struct F + { + int& operator()(int& i, void*) { return i; } + void* operator()(long, void* p) const { return p; } + }; + + int i = 5; + void* vp = &vp; // arbitrary void* value + constexpr F f; + + // Bound arg always forwarded as const int& so can only call second overload: + + auto g0 = bind_front(); // call wrapper has no bound arg + using G0 = decltype(g0); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p0 = static_cast(g0)(i, vp); + VERIFY( p0 == vp ); + + auto g1 = bind_front(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + static_assert(!is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p1 = static_cast(g1)(vp); + VERIFY( p1 == vp ); + + auto g2 = bind_front(std::ref(i)); // bound arg of type int& + using G2 = decltype(g2); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p2 = g2(vp); + VERIFY( p2 == vp ); + p2 = static_cast(g2)(vp); + VERIFY( p2 == vp ); + p2 = const_cast(g2)(vp); + VERIFY( p2 == vp ); + + auto g3 = bind_front(std::cref(i)); // bound arg of type const int& + using G3 = decltype(g3); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); +} + +void test03a() +{ + struct F + { + int& operator()(int& i, void*) { return i; } + void* operator()(long, void* p) const { return p; } + }; + + int i = 5; + void* vp = &vp; // arbitrary void* value + constexpr F f; + + // Bound arg always forwarded as const int& so can only call second overload: + auto g1 = bind_front(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + static_assert(is_invocable_r_v); + void* p1 = static_cast(g1)(vp); + VERIFY( p1 == vp ); +} + +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } + +consteval bool +test04() +{ + constexpr auto g = bind_front(); + VERIFY( std::is_empty_v ); + VERIFY( g(1, 2, 3) == 1 + 2*2 + 3*3 ); + constexpr auto g1 = bind_front(1); + VERIFY( g1(2, 3) == 1 + 2*2 + 3*3 ); + VERIFY( bind_front(1)(2, 3) == 1 + 2*2 + 3*3 ); + constexpr auto g2 = bind_front(1, 2); + VERIFY( g2(3) == 1 + 2*2 + 3*3 ); + VERIFY( bind_front(2)(3) == 1 + 2*2 + 3*3 ); + constexpr auto g3 = bind_front(1, 2, 3); + VERIFY( g3() == 1 + 2*2 + 3*3 ); + VERIFY(bind_front(3)() == 1 + 2*2 + 3*3 ); + return true; +} + +struct C { int i = 0; }; +struct D : C { D(){} D(D&&) { ++i; } }; +int f5(D const& d1, D const& d2, D const& d3) +{ return d1.i + d2.i + d3.i; } + +void test05() +{ + // Must move arguments into capture object, not construct in place + // like normal arguments. + VERIFY( bind_front(D{}, D{})(D{}) == 2 ); +} + +int +main() +{ + test01(); + test02(); + test02a(); + test03(); + test03a(); + static_assert(test04()); + test05(); +} diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp_neg.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp_neg.cc new file mode 100644 index 000000000000..d274c1916350 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp_neg.cc @@ -0,0 +1,38 @@ +// { dg-do compile { target c++26 } } + +#include + +void f() {} +using fp = decltype(&f); +constexpr fp nfp = nullptr; + +struct A { void mf() const {} }; +using mfp = decltype(&A::mf); +constexpr mfp nnmfp = &A::mf; +constexpr mfp nmfp = nullptr; + +struct B { B() = default; B(B const&) = delete; }; +void bf(B const&) {}; + +struct C { C() = default; C(C&&) = delete; }; +void cf(C&&) {}; + +int main() +{ + std::bind_front()(); + // Verify bind_front with fn a null pointer fails: + std::bind_front()(); // { dg-error "here" } + + std::bind_front(A{})(); + // Verify bind_front with mfn a null member pointer fails: + std::bind_front(A{})(); // { dg-error "here" } + + // Verify passing uncopyable type fails: + std::bind_front(B{}); // { dg-error "here" } + // + // Verify passing unmovable type fails: + std::bind_front(C{}); // { dg-error "here" } +} + +// { dg-error "static assertion failed" "" { target *-*-* } 0 } +// { dg-error "use of deleted function" "" { target *-*-* } 0 } diff --git a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc new file mode 100644 index 000000000000..d35d828f14c6 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc @@ -0,0 +1,100 @@ +// Test NTTP version of not_fn, from P2714 + +// { dg-do run { target c++26 } } + +#ifndef __cpp_lib_not_fn +# error "Feature test macro for not_fn is missing in " +#elif __cpp_lib_not_fn < 202306L +# error "Feature test macro for not_fn has wrong value in " +#endif + +#include +#include + +using std::not_fn; + +int func(int, char) { return 0; } + +struct F +{ + bool operator()() { return false; } + bool operator()() const { return true; } + bool operator()(int) const { return false; } +}; + +void +test01() +{ + auto f1 = not_fn(); + VERIFY( std::is_empty_v ); + VERIFY( f1(1, '2') == true ); + + auto f2 = not_fn<[] { return true; }>(); + VERIFY( std::is_empty_v ); + VERIFY( f2() == false ); + + auto f3 = not_fn(); + VERIFY( f3() == false ); // Prefer the const member. + VERIFY( f3(1) == true ); + const auto f4 = f3; + VERIFY( f4() == false ); +} + +void +test04() +{ + struct abstract { virtual void f() = 0; }; + struct derived : abstract { void f() { } }; + struct F { bool operator()(const abstract&) const { return false; } }; + constexpr F f; + constexpr derived d; + VERIFY( not_fn()(d) ); +} + +void +test05() +{ + auto nf = std::not_fn<[] { return false; }>(); + auto copy(nf); // PR libstdc++/70564 +} + +void +test06() +{ + struct Boolean { + Boolean operator!() const noexcept(false) { return *this; } + }; + struct F { + Boolean operator()() const { return {}; } + }; + const F f; + const auto notf = std::not_fn(); + using NotF = decltype(notf); + static_assert( std::is_invocable::value, "cannot negate" ); + static_assert( !noexcept(notf()), "conversion to bool affects noexcept" ); +} + +void +test07() +{ + struct NonNegatable { }; // there is no operator!(NonNegatable) + struct F { + NonNegatable operator()() const { return {}; } + }; + F f; + constexpr auto notf = std::not_fn(); + using NotF = decltype(notf); + static_assert( !std::is_invocable::value, "cannot negate" ); +} + +int +main() +{ + test01(); + test04(); + test05(); + test06(); + test07(); + constexpr auto f = []{ return false; }; + static_assert(std::not_fn()()); +} diff --git a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp_neg.cc b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp_neg.cc new file mode 100644 index 000000000000..78752735f392 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp_neg.cc @@ -0,0 +1,28 @@ +// { dg-do compile { target c++26 } } + +#include + +bool f() { return {}; } +using fp = decltype(&f); +constexpr fp nfp = nullptr; + +struct A { bool mf() const { return {}; } }; +using mfp = decltype(&A::mf); +constexpr mfp nnmfp = &A::mf; +constexpr mfp nmfp = nullptr; +constexpr A a; + +int main() +{ + (void) std::not_fn()(); + + // Verify not_fn with fn a null pointer fails. + (void) std::not_fn()(); // { dg-error "here" } + // + (void) std::not_fn()(a); + + // Verify not_fn with mfn a null member pointer fails. + return std::not_fn()(a); // { dg-error "here" } +} + +// { dg-error "static assertion failed" "" { target *-*-* } 0 } diff --git a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc index e3e92076f5c3..3ee367971ebe 100644 --- a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc +++ b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc @@ -57,6 +57,13 @@ namespace std { template _GLIBCXX14_CONSTEXPR binary_negate not2(const Predicate&); +#ifdef __cpp_lib_not_fn + template _GLIBCXX20_CONSTEXPR auto not_fn(F&&) + noexcept(std::is_nothrow_constructible, F&&>::value); +#if __cpp_lib_not_fn >= 202306L + template constexpr decltype(auto) not_fn() noexcept; +#endif +#endif // lib.binders, binders: template class binder1st; @@ -65,6 +72,24 @@ namespace std { template class binder2nd; template binder2nd bind2nd(const Operation&, const T&); +#ifdef __cpp_lib_bind_front + template + _GLIBCXX20_CONSTEXPR auto bind_front(F&&, Args&&...); +#if __cpp_lib_bind_front >= 202306L + template + constexpr decltype(auto) bind_front(Args&&...) + noexcept(__and_v...>); +#endif +#endif +#ifdef __cpp_lib_bind_back + template + _GLIBCXX20_CONSTEXPR auto bind_back(F&&, Args&&...); +#if __cpp_lib_bind_back >= 202306L + template + constexpr decltype(auto) bind_back(Args&&...) + noexcept(__and_v...>); +#endif +#endif // lib.function.pointer.adaptors, adaptors: template class pointer_to_unary_function; -- 2.47.3