This should be close to ready. However, std::is_invocable and noexcept still fail oddly applied to the not_fp result. The remaining failing test cases in */nttp.cc, commented out, need careful examination to see whether they should be expecting different results given that the argument function object is necessarily const.
Changes in v4: * For the no-bound-arguments case, bind_front and bind_back both return a zero-size _BindFn_t with static operator(). * For the normal case, the lambda functions returned are declared to yield std::invoke_result_t<> instead of decltype(auto), which produces different test outcomes. Changes in v3: * NTTP functions bind_front, bind_back are self-contained. * No tuples: bind_front and bind_back return a simple lambda. * bind_front, _back with no arguments simply return the template parameter function. * Forwarded-argument passing is disciplined. * NTTP not_fn uses a helper struct with static op(). * Many more of tests that pass non-NTTP versions also pass Add non-type template parameter function-object/-pointer argument versions of bind_front, bind_back, and not_fn. 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 * testsuite/20_util/function_objects/bind_back/nttp.cc * testsuite/20_util/function_objects/bind_front/1.cc * testsuite/20_util/function_objects/bind_front/nttp.cc * testsuite/20_util/function_objects/not_fn/nttp.cc * testsuite/20_util/headers/functional/synopsis.cc --- libstdc++-v3/include/bits/version.def | 12 + libstdc++-v3/include/bits/version.h | 21 +- libstdc++-v3/include/std/functional | 159 ++++++++++++- .../20_util/function_objects/bind_back/1.cc | 22 +- .../function_objects/bind_back/nttp.cc | 224 ++++++++++++++++++ .../20_util/function_objects/bind_front/1.cc | 16 +- .../function_objects/bind_front/nttp.cc | 218 +++++++++++++++++ .../20_util/function_objects/not_fn/nttp.cc | 113 +++++++++ .../20_util/headers/functional/synopsis.cc | 21 ++ 9 files changed, 781 insertions(+), 25 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_front/nttp.cc create mode 100644 libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 2f70a529927..7909a7b194a 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -463,6 +463,10 @@ ftms = { ftms = { name = not_fn; + values = { + v = 202306; + cxxmin = 26; + }; values = { v = 201603; cxxmin = 17; @@ -776,6 +780,10 @@ ftms = { ftms = { name = bind_front; + values = { + v = 202306; + cxxmin = 26; + }; values = { v = 201907; cxxmin = 20; @@ -784,6 +792,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 8e0ae682251..9721d1d23fe 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -511,7 +511,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 @@ -866,7 +871,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 @@ -876,7 +886,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 307bcb95bcc..03375e2a32a 100644 --- a/libstdc++-v3/include/std/functional +++ b/libstdc++-v3/include/std/functional @@ -71,6 +71,7 @@ #include <type_traits> #include <bits/functional_hash.h> #include <bits/invoke.h> +#include <bits/move.h> #include <bits/refwrap.h> // std::reference_wrapper and _Mem_fn_traits #if _GLIBCXX_HOSTED # include <bits/std_function.h> // std::function @@ -921,6 +922,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION std::forward<_BoundArgs>(__args)...); } +#if __cpp_lib_bind_front >= 202306 || __cpp_lib_bind_front >= 202306 + template <auto __fn> + struct _BindFn_t + { + using _Fn = decltype(__fn); + + template <typename... _Args> + constexpr static auto + operator()(_Args... __args) + noexcept(is_nothrow_invocable_v<_Fn, _Args...>) + -> invoke_result_t< _Fn, _Args...> + requires (is_invocable_v< _Fn, _Args...>) + { return invoke(__fn, forward<_Args>(__args)...); } + + void operator=(this auto&&, _BindFn_t&&) = delete; + }; +#endif + #ifdef __cpp_lib_bind_front // C++ >= 20 template<typename _Fd, typename... _BoundArgs> @@ -940,7 +959,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _M_bound_args(std::forward<_Args>(__args)...) { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); } -#if __cpp_explicit_this_parameter +#ifdef __cpp_explicit_this_parameter template<typename _Self, typename... _CallArgs> constexpr invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self, _BoundArgs>..., _CallArgs...> @@ -1049,6 +1068,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return _Bind_front_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); } + +#if __cpp_lib_bind_front >= 202306 + + /** Create call wrapper by partial application of arguments to function. + * + * The result of `std::bind_front<fn>(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<auto __fn, typename... _BindArgs> + constexpr decltype(auto) + bind_front(_BindArgs&&... __bind_args) + noexcept(__and_<is_nothrow_constructible<_BindArgs>...>::value) + requires ( + (is_constructible_v<decay_t<_BindArgs>, _BindArgs> and ...) and + (is_move_constructible_v<decay_t<_BindArgs>> and ...)) + { + using _Fn = decltype(__fn); + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { + static_assert(__fn != nullptr); + } + if constexpr (sizeof...(_BindArgs) == 0) { + return _BindFn_t<__fn>{}; + } else { + // Capture arguments in a lambda and return that. + return [... __bound_args(std::forward<_BindArgs>(__bind_args))] + <typename _Self, typename... _CallArgs> + (this _Self&&, _CallArgs&&... __call_args) + noexcept(is_nothrow_invocable_v< + _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>) + -> invoke_result_t< + _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...> + requires (is_invocable_v< + _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>) + { + return invoke(__fn, + forward_like<_Self>(__bound_args)..., + forward<_CallArgs>(__call_args)...); + }; + } + } + +#endif // __cpp_lib_bind_front // C++26 #endif // __cpp_lib_bind_front #ifdef __cpp_lib_bind_back // C++ >= 23 @@ -1118,6 +1183,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 >= 202306 + + /** Create call wrapper by partial application of arguments to function. + * + * The result of `std::bind_back<fn>(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<auto __fn, typename... _BindArgs> + constexpr decltype(auto) + bind_back(_BindArgs&&... __bind_args) + noexcept(__and_<is_nothrow_constructible<_BindArgs>...>::value) + requires ( + (is_constructible_v<decay_t<_BindArgs>, _BindArgs> and ...) and + (is_move_constructible_v<decay_t<_BindArgs>> and ...)) + { + using _Fn = decltype(__fn); + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { + static_assert(__fn != nullptr); + } + if constexpr (sizeof...(_BindArgs) == 0) { + return _BindFn_t<__fn>{}; + } else { + // Capture arguments in a lambda and return that. + return [... __bound_args(std::forward<_BindArgs>(__bind_args))] + <typename _Self, typename... _CallArgs> + (this _Self&&, _CallArgs&&... __call_args) + noexcept(is_nothrow_invocable_v< + _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>) + -> invoke_result_t< + _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...> + requires (is_invocable_v< + _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>) + { + return invoke(__fn, + forward<_CallArgs>(__call_args)..., + forward_like<_Self>(__bound_args)...); + }; + } + } + +#endif // __cpp_lib_bind_back // C++26, nttp #endif // __cpp_lib_bind_back #if __cplusplus >= 201402L @@ -1212,13 +1323,55 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ template<typename _Fn> _GLIBCXX20_CONSTEXPR - inline auto + inline decltype(auto) not_fn(_Fn&& __fn) noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>, _Fn&&>::value) { return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; } -#endif + +#if __cpp_lib_not_fn >= 202306 + + template<auto __fn> + struct _Not_fn_nttp + { + template<typename... _Args> + constexpr static decltype(auto) + operator()(_Args&&... __args) + noexcept( noexcept( + not std::invoke(__fn, std::forward<_Args>(__args)...) )) + requires requires { + not std::invoke(__fn, std::forward<_Args>(__args)...); } + { + return not std::invoke(__fn, std::forward<_Args>(__args)...); + } + }; + + /** 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<auto __fn> + constexpr decltype(auto) + not_fn() noexcept + { + using _Fn = decltype(__fn); + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { + static_assert(__fn != nullptr); + } + return _Not_fn_nttp<__fn>{}; + } + +#endif // __cpp_lib_not_fn >= 202306 +#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 c31d3228815..feedead477b 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 @@ -149,23 +149,23 @@ test03() static_assert(is_invocable_r_v<void*, const G4&&>); } -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 +constexpr int test04() { auto g = bind_back(f); - VERIFY( g(1, 2, 3) == 1 + 2*(2 + 3) ); + if (!( g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7; 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) ); + if (!( g1(2, 3) == 3*1 + 2 + 3*2)) return 6; + if (!( bind_back(g, 1)(2, 3) == 3*1 + 2 + 2*3 )) return 5; auto g2 = bind_back(f, 1, 2); - VERIFY( g2(3) == 3 + 2*(1 + 2) ); - VERIFY( bind_back(g1, 2)(3) == 3 + 2*(2 + 1) ); + if (!( g2(3) == 3 + 2*1 + 3*2)) return 4; + if (!( bind_back(g1, 2)(3) == 3*1 + 2*2 + 3 )) return 3; auto g3 = bind_back(f, 1, 2, 3); - VERIFY( g3() == 1 + 2*(2 + 3) ); - VERIFY( bind_back(g2, 3)() == 3 + 2*(1 + 2) ); - return true; + if (!( g3() == 1 + 2*2 + 3*3 )) return 2; + if (!( bind_back(g2, 3)() == 3*1 + 1*2 + 2*3)) return 1; + return 0; } int @@ -174,5 +174,5 @@ main() test01(); test02(); test03(); - static_assert(test04()); + static_assert(test04() == 0); } 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 00000000000..23e5fe850c2 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc @@ -0,0 +1,224 @@ +// { dg-do run { target c++26 } } +// { dg-add-options no_pch } + +// Test NTTP bind_back<f>(Args...), P2714 + +// #define TRY +// #define TRY_CT // turn on static asserts + +#include <functional> + +#ifndef __cpp_lib_bind_back +# error "Feature test macro for bind_back is missing in <functional>" +#elif __cpp_lib_bind_back < 202306L +# error "Feature test macro for bind_back has wrong value in <functional>" +#endif + +#include <testsuite_hooks.h> + +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()() {} }; + constexpr F f{}; + + // [The following differ from the non-NTTP version of bind_back.] + // Arguments should be decayed: +#ifdef TRY_CT + // [The following differs from the non-NTTP version of bind_back.] + static_assert(std::is_same_v< + decltype(bind_back<f>(std::declval<int>())), + decltype(bind_back<f>(std::declval<int&>())) + >); + static_assert(std::is_same_v< + decltype(bind_back<f>(std::declval<int>())), + decltype(bind_back<f>(std::declval<const int&>())) + >); +#endif + + // Reference wrappers should be handled: + static_assert(!std::is_same_v< + decltype(bind_back<f>(std::declval<int&>())), + decltype(bind_back<f>(std::ref(std::declval<int&>()))) + >); + static_assert(!std::is_same_v< + decltype(bind_back<f>(std::declval<const int&>())), + decltype(bind_back<f>(std::cref(std::declval<int&>()))) + >); + static_assert(!std::is_same_v< + decltype(bind_back<f>(std::ref(std::declval<int&>()))), + decltype(bind_back<f>(std::cref(std::declval<int&>()))) + >); +} + +void +test02() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + + struct F + { + quals operator()(int) & { return { false, true }; } + quals operator()(int) const & { return { true, true }; } + quals operator()(int) && { return { false, false }; } + quals operator()(int) const && { return { true, false }; } + }; + + { + constexpr F f; + auto g = bind_back<f>(); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? +#if TRY + // [The following differs from the non-NTTP version of bind_back.] + 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 ); +#endif + } + { + constexpr F f; + auto g = bind_back<f>(0); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? +#ifdef TRY + // [The following differs from the non-NTTP version of bind_back.] + 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 ); +#endif + } +} + +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; + auto g1 = bind_back<f>(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + // Invoking G1& will pass g1's bound arg as int&, so calls first overload: +#ifdef TRY_CT + // [The following differs from the non-NTTP version of bind_back.] + static_assert(!is_invocable_r_v<int&, G1&, void*>); +#endif + + // Invoking const G1& or G&& calls second overload: + static_assert(is_invocable_r_v<void*, const G1&, void*>); + static_assert(is_invocable_r_v<void*, G1&&, void*>); + void* p1 = static_cast<G1&&>(g1)(vp); + VERIFY( p1 == vp ); + + // And can call first overload on const G6: + auto g2 = bind_back<f>(std::ref(i)); // bound arg of type int& + using G2 = decltype(g2); + // Bound arg always forwarded as int& even from G2&& or const G2& +#ifdef TRY_CT + // [The following differ from the non-NTTP version of bind_back.] + static_assert(is_invocable_r_v<int&, G2&, void*>); + static_assert(is_invocable_r_v<int&, G2&&, void*>); +#endif + + // But cannot call first overload on const G2: +#ifdef TRY_CT + // [The following differs from the non-NTTP version of bind_back.] + static_assert(is_invocable_r_v<void*, const G2&, void*>); + static_assert(is_invocable_r_v<void*, const G2&&, void*>); + void* i2 = g2(vp); + VERIFY( &i2 == &i ); + void* i2r = static_cast<G2&&>(g2)(vp); + VERIFY( iv2r == &i ); + void* p2 = const_cast<const G2&>(g2)(vp); + VERIFY( p2 == vp ); +#endif + + auto g3 = bind_back<f>(std::cref(i)); // bound arg of type const int& + using G3 = decltype(g3); + // Bound arg always forwarded as const int& so can only call second overload: + static_assert(is_invocable_r_v<void*, G3&, void*>); + static_assert(is_invocable_r_v<void*, G3&&, void*>); + static_assert(is_invocable_r_v<void*, const G3&, void*>); + static_assert(is_invocable_r_v<void*, const G3&&, void*>); + +#ifdef TRY_CT + // [The following differ from the non-NTTP version of bind_back.] + // auto g4 = bind_back<g2>(nullptr); + // using G4 = decltype(g4); + // static_assert(is_invocable_r_v<int&, G4&>); + // static_assert(is_invocable_r_v<int&, G4&&>); + // static_assert(is_invocable_r_v<void*, const G4&>); + // static_assert(is_invocable_r_v<void*, const G4&&>); +#endif +} + +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } + +consteval int +test04() +{ + constexpr auto g = bind_back<f>(); + VERIFY( std::is_empty_v<decltype(g)> ); + if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7; + constexpr auto g1 = bind_back<f>(1); + if (!(g1(2, 3) == 3*1 + 1*2 + 2*3 )) return 6; + if (!(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 )) return 5; + constexpr auto g2 = bind_back<f>(1, 2); + if (!(g2(3) == 2*1 + 3*2 + 1*3 )) return 4; + if (!(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 )) return 3; + constexpr auto g3 = bind_back<f>(1, 2, 3); + if (!(g3() == 1 + 2*2 + 3*3)) return 2; + if (!(bind_back<g2>(3)() == 1*2 + 2*3 + 3*1 )) return 1; + return 0; +} + +template <auto nnfp, auto nfp> +void test05() { + VERIFY(bind_back<nnfp>(1)(2, 3) == 3*1 + 1*2 + 2*3); +#ifdef TRY_CT + // [Fails to sandbox the static_assert(fp) in bind_back<fp>():] + VERIFY(!requires { bind_back<nfp>(1); }); +#endif +} + +int +main() +{ + test01(); + test02(); + test03(); + static_assert(test04() == 0); + constexpr int (*nnfp)(int, int, int) = f; + constexpr int (*nfp)(int, int, int) = nullptr; + test05<nnfp, nfp>(); +} 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 57482c52263..b038889fbb4 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 @@ -149,22 +149,22 @@ test03() static_assert(is_invocable_r_v<void*, const G4&&>); } -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 ); } int 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 00000000000..c06b153ee28 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc @@ -0,0 +1,218 @@ +// { dg-do run { target c++26 } } +// { dg-add-options no_pch } + +// Test NTTP bind_front<f>(Args...), P2714 + +// #define TRY +// #define TRY_CT // turn on static asserts + +#include <functional> + +#ifndef __cpp_lib_bind_front +# error "Feature test macro for bind_front is missing in <functional>" +#elif __cpp_lib_bind_front < 201902L +# error "Feature test macro for bind_front has wrong value in <functional>" +#endif + +#include <testsuite_hooks.h> + +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()() {} }; + constexpr F f{}; + + // Arguments should be decayed: +#ifdef TRY_CT + // [The following differ from the non-NTTP version of bind_front.] + static_assert(std::is_same_v< + decltype(bind_front<f>(std::declval<int>())), + decltype(bind_front<f>(std::declval<int&>())) + >); + static_assert(std::is_same_v< + decltype(bind_front<f>(std::declval<int>())), + decltype(bind_front<f>(std::declval<const int&>())) + >); +#endif + + // Reference wrappers should be handled: + static_assert(!std::is_same_v< + decltype(bind_front<f>(std::declval<int&>())), + decltype(bind_front<f>(std::ref(std::declval<int&>()))) + >); + static_assert(!std::is_same_v< + decltype(bind_front<f>(std::declval<const int&>())), + decltype(bind_front<f>(std::cref(std::declval<int&>()))) + >); + static_assert(!std::is_same_v< + decltype(bind_front<f>(std::ref(std::declval<int&>()))), + decltype(bind_front<f>(std::cref(std::declval<int&>()))) + >); +} + +void +test02() +{ + struct quals + { + bool as_const; + bool as_lvalue; + }; + + struct F + { + quals operator()(int) & { return { false, true }; } + quals operator()(int) const & { return { true, true }; } + quals operator()(int) && { return { false, false }; } + quals operator()(int) const && { return { true, false }; } + }; + + { + constexpr F f; + auto g = bind_front<f>(); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? +#ifdef TRY + // [The following differ from the non-NTTP version of bind_front.] + 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 ); +#endif + } + { + constexpr F f; + auto g = bind_front<f>(0); + const auto& cg = g; + quals q; + + // Constness and value category forwarded to the target object? +#ifdef TRY + // [The following differ from the non-NTTP version of bind_front.] + 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 ); +#endif + } +} + +void +test03() +{ + struct F + { + int& operator()(int& i, void*) { return i; } + void* operator()(int, void* p) const { return p; } + }; + + int i = 5; + void* vp = &vp; // arbitrary void* value + + constexpr F f; + auto g1 = bind_front<f>(i); // call wrapper has bound arg of type int + using G1 = decltype(g1); + // Invoking G1& will pass g1's bound arg as int&, so calls first overload: +#ifdef TRY_CT + // [The following differs from the non-NTTP version of bind_front.] + static_assert(is_invocable_r_v<int&, G1&, void*>); +#endif + + // Invoking const G1& or G&& calls second overload: + static_assert(is_invocable_r_v<void*, const G1&, void*>); + static_assert(is_invocable_r_v<void*, G1&&, void*>); + void* p1 = static_cast<G1&&>(g1)(vp); + VERIFY( p1 == vp ); + + // And can call first overload on const G6: + auto g2 = bind_front<f>(std::ref(i)); // bound arg of type int& + using G2 = decltype(g2); + // Bound arg always forwarded as int& even from G2&& or const G2& +#ifdef TRY_CT + // [The following differ from the non-NTTP version of bind_front.] + static_assert(is_invocable_r_v<int&, G2&, void*>); + static_assert(is_invocable_r_v<int&, G2&&, void*>); + // But cannot call first overload on const G2: + static_assert(is_invocable_r_v<void*, const G2&, void*>); + static_assert(is_invocable_r_v<void*, const G2&&, void*>); + int& i2 = g2(vp); + VERIFY( &i2 == &i ); + int& i2r = static_cast<G2&&>(g2)(vp); + VERIFY( &i2r == &i ); + void* p2 = const_cast<const G2&>(g2)(vp); + VERIFY( p2 == vp ); +#endif + + auto g3 = bind_front<f>(std::cref(i)); // bound arg of type const int& + using G3 = decltype(g3); + // Bound arg always forwarded as const int& so can only call second overload: + static_assert(is_invocable_r_v<void*, G3&, void*>); + static_assert(is_invocable_r_v<void*, G3&&, void*>); + static_assert(is_invocable_r_v<void*, const G3&, void*>); + static_assert(is_invocable_r_v<void*, const G3&&, void*>); + +#ifdef TRY_CT + // [The following differ from the non-NTTP version of bind_front.] + auto g4 = bind_front<g2>(nullptr); + using G4 = decltype(g4); + static_assert(is_invocable_r_v<int&, G4&>); + static_assert(is_invocable_r_v<int&, G4&&>); + static_assert(is_invocable_r_v<void*, const G4&>); + static_assert(is_invocable_r_v<void*, const G4&&>); +#endif +} + +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } + +consteval int +test04() +{ + constexpr auto g = bind_front<f>(); + VERIFY( std::is_empty_v<decltype(g)> ); + if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7; + constexpr auto g1 = bind_front<f>(1); + if (!(g1(2, 3) == 1 + 2*2 + 3*3 )) return 6; + if (!(bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 )) return 5; + constexpr auto g2 = bind_front<f>(1, 2); + if (!(g2(3) == 1 + 2*2 + 3*3 )) return 4; + if (!(bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 )) return 3; + constexpr auto g3 = bind_front<f>(1, 2, 3); + if (!(g3() == 1 + 2*2 + 3*3)) return 2; + if (!(bind_front<g2>(3)() == 1 + 2*2 + 3*3 )) return 1; + return 0; +} + +template <auto nnfp, auto nfp> +constexpr void test05() { + VERIFY(bind_front<nnfp>(1)(2, 3) == 14); +#ifdef TRY_CT // Fails to sandbox the static_assert(fp) in bind_front<fp>(): + VERIFY(!requires { bind_front<nfp>(1); }); +#endif +} + +int +main() +{ + test01(); + test02(); + test03(); + static_assert(test04() == 0); + constexpr int (*nnfp)(int, int, int) = f; + constexpr int (*nfp)(int, int, int) = nullptr; + test05<nnfp, nfp>(); +} 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 00000000000..b9a74bd16c6 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc @@ -0,0 +1,113 @@ +// Test NTTP version of not_fn, from P2714 + +// { dg-do run { target c++26 } } + +// #define TRY +// #define TRY_CT // turn on failing static asserts + +#ifndef __cpp_lib_bind_back +# error "Feature test macro for bind_back is missing in <functional>" +#elif __cpp_lib_bind_back < 202306L +# error "Feature test macro for bind_back has wrong value in <functional>" +#endif + +#include <functional> +#include <testsuite_hooks.h> + +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<func>(); + VERIFY( std::is_empty_v<decltype(f1)> ); + VERIFY( f1(1, '2') == true ); + + auto f2 = not_fn<[] { return true; }>(); + VERIFY( std::is_empty_v<decltype(f2)> ); + VERIFY( f2() == false ); + + auto f3 = not_fn<F{}>(); + 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()(abstract&) const { return false; } }; + constexpr F f; + derived d; + VERIFY( not_fn<f>()(d) ); +} + +void +test05() +{ + auto nf = std::not_fn<[] { return false; }>(); + auto copy(nf); // PR libstdc++/70564 +} + +void +test06() +{ + struct Boolean { + Boolean operator!() noexcept(false) { return *this; } + }; + struct F { + Boolean operator()() { return {}; } + }; + F f; + auto notf = std::not_fn<f>(); + using NotF = decltype(notf); + + // [This fails for NTTP not_fn. ] +#ifdef TRY_CT + static_assert( std::is_invocable<NotF>::value, "cannot negate" ); +#endif + + // [This check fails for NTTP not_fn, even though actually invoking it fails. ] +#ifdef TRY_CT + static_assert( !noexcept(notf()), "conversion to bool affects noexcept" ); +#endif +} + +void +test07() +{ + struct NonNegatable { }; // there is no operator!(NonNegatable) + struct F { + NonNegatable operator()() const { return {}; } + }; + F f; + constexpr auto notf = std::not_fn<f>(); + using NotF = decltype(notf); + +#ifdef TRY_CT + // [This check fails for NTTP not_fn, even though actually invoking it fails. ] + static_assert( !std::is_invocable<NotF>::value, "cannot negate" ); +#endif +} + +int +main() +{ + test01(); + test04(); + test05(); + test06(); + test07(); +} diff --git a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc index e3e92076f5c..5e835d684fd 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 <class Predicate> _GLIBCXX14_CONSTEXPR binary_negate<Predicate> not2(const Predicate&); +#ifdef __cpp_lib_not_fn + template <typename F> _GLIBCXX20_CONSTEXPR auto not_fn(F&&) + noexcept(std::is_nothrow_constructible<std::decay_t<F>, F&&>::value); +#if __cpp_lib_not_fn >= 2020306 + template <auto f> constexpr auto not_fn() noexcept; +#endif +#endif // lib.binders, binders: template <class Operation> class binder1st; @@ -65,6 +72,20 @@ namespace std { template <class Operation> class binder2nd; template <class Operation, class T> binder2nd<Operation> bind2nd(const Operation&, const T&); +#ifdef __cpp_lib_bind_front + template <typename F, typename... Args> + _GLIBCXX20_CONSTEXPR auto bind_front(F&&, Args&&...); +#if __cpp_lib_bind_front >= 202306 + template <auto f, typename... Args> constexpr auto bind_front(Args&&...); +#endif +#endif +#ifdef __cpp_lib_bind_back + template <typename F, typename... Args> + _GLIBCXX20_CONSTEXPR auto bind_back(F&&, Args&&...); +#if __cpp_lib_bind_back >= 202306 + template <auto f, typename... Args> constexpr auto bind_back(Args&&...); +#endif +#endif // lib.function.pointer.adaptors, adaptors: template <class Arg, class Result> class pointer_to_unary_function; -- 2.50.0