On Fri, 18 Jul 2025 at 12:22, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > On Fri, Jul 18, 2025 at 12:52 PM Nathan Myers <n...@cantrip.org> wrote: >> >> * I rewrote the implementation to more closely resemble the >> non-NTTP version, and fix the various noexcept and requires >> omissions. > > I have a task to adjust them later, so the alignment may not be desired. >> >> >> * I don't understand what "a separate type for single argument >> case, that correspond to function_ref nontype, ptr/ref constructor" >> means, but I did make no-bound-arguments overloads. > > I was thinking about having: > template<auto __fn> > struct _Fn_t // No bound argument, could be used for both bind_front and > bind_back. > { > template<typename... Argos> > static operator()(_Args&&... args); > }; > > template<auto __fn, typename T> > struct _Bind_front1_t // Only one bind argument > { > T arg; > template<typename... Argos> > static operator()(_Args&&... args); > > // delete assignments, but keep copy and move > }; > // The function_ref has (non_type<f>, ref) that does not dangle, > // then we could also make _Bind_front_1 to do the same. > > And then having in bind_front() > if constexpr (sizeof(_BoundsArgs) == 0) > return _Fn_t<__fn>(); > else if constexpr (sizeof(_BoundsArgs) == 1) > return _Bind_front1_t<__fn>(); > else > // use lambda, to avoid instantiating tuple
I like this direction. For no bound args we reuse the type and so have fewer different class templates flying around the program, and we avoid instantiating std::tuple. > > Similary bind_back, could use __Fn_t. > >> >> >> * P2714R1 has "Mandates: If is_pointer_v<F> || is_member_pointer_v<F> >> is true, then f != nullptr is true." This seems to require the >> "if constexpr" statements as written. > > Thanks, I missed that. Indeed this is correct. >> >> >> * Some of the tests have results that differ from the non-NTTP >> results. Some differences are a consequence of different const >> qualification, as the argument callable is necessarily const. >> Others I don't understand, e.g. std::invocable_v reporting a call >> would work, where actually invoking it fails. Differences from >> the non-NTTP */1.cc tests are commented in */nttp.cc . >> >> 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 | 230 +++++++++++++++++- >> .../20_util/function_objects/bind_back/1.cc | 22 +- >> .../function_objects/bind_back/nttp.cc | 191 +++++++++++++++ >> .../20_util/function_objects/bind_front/1.cc | 16 +- >> .../function_objects/bind_front/nttp.cc | 188 ++++++++++++++ >> .../20_util/function_objects/not_fn/nttp.cc | 101 ++++++++ >> .../20_util/headers/functional/synopsis.cc | 21 ++ >> 9 files changed, 778 insertions(+), 24 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..37c4193bdb2 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 >> @@ -940,7 +941,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 +1050,92 @@ _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 >> + >> + template<auto __f, typename... _BoundArgs> >> + struct _Bind_front_nttp >> + { >> + static_assert((is_move_constructible_v<_BoundArgs> && ...)); >> + >> + using _Fn = decltype(__f); >> + using _BoundIndices = index_sequence_for<_BoundArgs...>; >> + >> + // First parameter ensures this constructor is not confused >> + // with a copy or move constructor. >> + template<typename... _FwdArgs> // derived from _BoundArgs >> + constexpr explicit >> + _Bind_front_nttp(int, _FwdArgs&&... __args) >> + noexcept(__and_<is_nothrow_constructible<_BoundArgs, _FwdArgs>... >> + >::value) >> + : _M_bound_args(std::forward<_FwdArgs>(__args)...) >> + { static_assert(sizeof...(_FwdArgs) == sizeof...(_BoundArgs)); } >> + >> + template<typename _Self, typename... _CallArgs> >> + constexpr auto > > This should be decltype(auto), we need to preserve reference returns. >> >> + operator()(this _Self&& __self, _CallArgs&&... __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, __like_t<_Self, _BoundArgs>..., _CallArgs...>) >> + requires (is_invocable_v< >> + _Fn, __like_t<_Self, _BoundArgs>..., _CallArgs...>) >> + { >> + return _S_call(__like_t<_Self, _Bind_front_nttp>(__self), >> + _BoundIndices(), std::forward<_CallArgs>(__call_args)...); >> + } >> + >> + private: >> + >> + template<typename _Self, size_t... _Ind, typename... _CallArgs> >> + constexpr static >> + decltype(auto) >> + _S_call(_Self&& __g, index_sequence<_Ind...>, _CallArgs&&... >> __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, __like_t<_Self, _BoundArgs>..., _CallArgs...>) >> + { >> + return std::invoke(__f, >> + std::get<_Ind>(std::forward<_Self>(__g)._M_bound_args)..., >> + std::forward<_CallArgs>(__call_args)...); >> + } >> + >> + [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args; >> + }; >> + >> + /** Create call wrapper by partial application of arguments to function. >> + * >> + * The result of `std::bind_front<f>(args...)` is a function object that >> + * stores the bound arguments, `args...`. When that function object is >> + * invoked with `call_args...` it returns the result of calling >> + * `f(args..., call_args...)`. >> + * >> + * @since C++26 >> + */ >> + template<auto __fn> >> + constexpr auto >> + bind_front() >> + noexcept >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return __fn; >> + } >> + >> + template<auto __fn, typename... _Args> >> + constexpr auto >> + bind_front(_Args&&... __args) >> + noexcept(__and_<is_nothrow_constructible<_Args>...>::value) >> + requires ( >> + (is_constructible_v<decay_t<_Args>, _Args> and ...) and >> + (is_move_constructible_v<decay_t<_Args>> and ...)) >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return _Bind_front_nttp<__fn, _Args...>(0, >> std::forward<_Args>(__args)...); >> + } >> +#endif // __cpp_lib_bind_front // C++26 >> #endif // __cpp_lib_bind_front >> >> #ifdef __cpp_lib_bind_back // C++ >= 23 >> @@ -1118,6 +1205,92 @@ _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 >> + >> + template<auto __f, typename... _BoundArgs> >> + struct _Bind_back_nttp >> + { >> + static_assert((is_move_constructible_v<_BoundArgs> && ...)); >> + >> + using _Fn = decltype(__f); >> + using _BoundIndices = index_sequence_for<_BoundArgs...>; >> + >> + // First parameter ensures this constructor is not confused >> + // with a copy or move constructor. >> + template<typename... _FwdArgs> // derived from _BoundArgs >> + constexpr explicit >> + _Bind_back_nttp(int, _FwdArgs&&... __args) >> + noexcept(__and_<is_nothrow_constructible<_BoundArgs, _FwdArgs>... >> + >::value) >> + : _M_bound_args(std::forward<_FwdArgs>(__args)...) >> + { static_assert(sizeof...(_FwdArgs) == sizeof...(_BoundArgs)); } >> + >> + template<typename _Self, typename... _CallArgs> >> + constexpr auto >> + operator()(this _Self&& __self, _CallArgs&&... __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, _CallArgs..., __like_t<_Self, _BoundArgs>...>) >> + requires (is_invocable_v< >> + _Fn, _CallArgs..., __like_t<_Self, _BoundArgs>...>) >> + { >> + return _S_call(__like_t<_Self, _Bind_back_nttp>(__self), >> + _BoundIndices(), std::forward<_CallArgs>(__call_args)...); >> + } >> + >> + private: >> + >> + template<typename _Self, size_t... _Ind, typename... _CallArgs> >> + constexpr static >> + decltype(auto) >> + _S_call(_Self&& __g, index_sequence<_Ind...>, _CallArgs&&... >> __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, _CallArgs..., __like_t<_Self, _BoundArgs>...>) >> + { >> + return std::invoke(__f, >> + std::forward<_CallArgs>(__call_args)..., >> + std::get<_Ind>(std::forward<_Self>(__g)._M_bound_args)...); >> + } >> + >> + [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args; >> + }; >> + >> + /** Create call wrapper by partial application of arguments to function. >> + * >> + * The result of `std::bind_back<f>(args...)` is a function object that >> + * stores the bound arguments, `args...`. When that function object is >> + * invoked with `call_args...` it returns the result of calling >> + * `f(call_args..., args...)`. >> + * >> + * @since C++26 >> + */ >> + template<auto __fn> >> + constexpr auto >> + bind_back() >> + noexcept >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return __fn; >> + } >> + >> + template<auto __fn, typename... _Args> >> + constexpr auto >> + bind_back(_Args&& ...__args) >> + noexcept(__and_<is_nothrow_constructible<_Args>...>::value) >> + requires ( >> + (is_constructible_v<decay_t<_Args>, _Args> and ...) and >> + (is_move_constructible_v<decay_t<_Args>> and ...)) >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return _Bind_back_nttp<__fn, _Args...>(0, >> std::forward<_Args>(__args)...); >> + } >> +#endif // __cpp_lib_bind_back // C++26, nttp >> #endif // __cpp_lib_bind_back >> >> #if __cplusplus >= 201402L >> @@ -1218,7 +1391,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> { >> return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; >> } >> -#endif >> + >> +#if __cpp_lib_not_fn >= 202306 >> + /// Generalized negator, empty object. >> + template<auto __fn> >> + class _Not_fn_nttp >> + { >> + using _Fn = decltype(__fn); >> + template<typename _Tp> >> + constexpr static >> + decltype(! declval<_Tp>()) >> + _S_not(_Tp&&) noexcept(noexcept(! declval<_Tp>())); >> + >> + public: >> + template<typename... _Args> > > >> + constexpr static auto > > Again decltype(auto). >> >> + operator()(_Args&&... __args) > > I think it may be just simpler for you to do: > requires requires { > !std::invoke(__fn, std::forward<_Args>(__args)...) > } > noexcept(noexcept(!std::invoke(__fn, > std::forward<_Args>(__args)...)) > >> >> + noexcept( >> + is_nothrow_invocable_v<_Fn, _Args...> and >> + noexcept(_S_not<invoke_result_t<_Fn, _Args...>>( >> + declval<invoke_result_t<_Fn, _Args...>>()) )) >> + requires ( >> + is_invocable_v<_Fn, _Args...> and >> + is_invocable_v< >> + decltype(_S_not<invoke_result_t<_Fn, _Args...>>), >> + invoke_result_t<_Fn, _Args...> >) >> + { >> + return ! 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 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..467502c7473 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc >> @@ -0,0 +1,191 @@ >> +// { dg-do run { target c++26 } } >> +// { dg-add-options no_pch } >> + >> +// Test NTTP bind_back<f>(Args...), P2714 >> + >> +#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: >> + // 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&>())) >> + // >); >> + >> + // 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()() & { return { false, true }; } >> + quals operator()() const & { return { true, true }; } >> + quals operator()() && { return { false, false }; } >> + quals operator()() 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? >> + 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 >> +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: >> + // [The following differs from the non-NTTP version of bind_back.] >> + static_assert(!is_invocable_r_v<int&, G1&, void*>); >> + >> + // 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& >> + // [The following differs 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*>); >> + >> + // But cannot call first overload on const G2: >> + // [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*>); >> + // [The following differ from the non-NTTP version of bind_back.] >> + //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 ); >> + >> + 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*>); >> + >> + // [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&&>); >> +} >> + >> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } >> + >> +consteval int >> +test04() >> +{ >> + constexpr auto g = bind_back<f>(); >> + 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; >> + >> + // [The following differs from the non-NTTP version of bind_back.] >> + // 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; >> + >> + // [The following differs from the non-NTTP version of bind_back.] >> + // 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); >> +#if 0 // 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..a16adfbd379 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc >> @@ -0,0 +1,188 @@ >> +// { dg-do run { target c++26 } } >> +// { dg-add-options no_pch } >> + >> +// Test NTTP bind_front<f>(Args...), P2714 >> + >> +#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{}; >> + >> + // [The following differ from the non-NTTP version of bind_front.] >> + // Arguments should be decayed: >> + // 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&>())) >> + // >); >> + >> + // 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()() & { return { false, true }; } >> + quals operator()() const & { return { true, true }; } >> + quals operator()() && { return { false, false }; } >> + quals operator()() 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? >> + 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 >> +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: >> + // [The following differs from the non-NTTP version of bind_front.] >> + // static_assert(is_invocable_r_v<int&, G1&, void*>); >> + >> + // 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& >> + // static_assert(is_invocable_r_v<int&, G2&, void*>); >> + // static_assert(is_invocable_r_v<int&, G2&&, void*>); >> + >> + // [The following differ from the non-NTTP version of bind_front.] >> + // 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 ); >> + >> + 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*>); >> + >> + // [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&&>); >> +} >> + >> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; } >> + >> +consteval int >> +test04() >> +{ >> + constexpr auto g = bind_front<f>(); >> + 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; >> + >> + // [The following differ from the non-NTTP version of bind_front.] >> + // 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; >> + >> + // [The following differ from the non-NTTP version of bind_front.] >> + // 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); >> +#if 0 // 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..b7c06c103a7 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc >> @@ -0,0 +1,101 @@ >> +// Test NTTP version of not_fn, from P2714 >> + >> +// { dg-do run { target c++26 } } >> + >> +#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( f1(1, '2') == true ); >> + >> + auto f2 = not_fn<[] { return true; }>(); >> + 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 check fails for NTTP not_fn, even though actually invoking it >> fails. ] >> + // static_assert( std::is_invocable<NotF>::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<f>(); >> + using NotF = decltype(notf); >> + >> + // [This check fails for NTTP not_fn, even though actually invoking it >> fails. ] >> + // static_assert( !std::is_invocable<NotF>::value, "cannot negate" ); >> +} >> + >> +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 >>