[PATCHv7] libstdc++: Add NTTP bind_front, -back, not_fn (P2714) [PR119744]
Tomasz Kaminski
tkaminsk@redhat.com
Wed Jul 30 16:11:31 GMT 2025
On Wed, Jul 30, 2025 at 12:48 AM Nathan Myers <ncm@cantrip.org> wrote:
> Changes in v7:
> * Clean up comments, inactive cases in tests.
> * Restore symmetry of tests for bind_back and bind_front
> * Add another test for type qualifier propagation, per review
> * Add test verifying arguments are properly moved into the capture
> object.
>
> Changes in v6:
> * Remove deletion of op= for _Bind_fn_t.
> * Note no need for change to exports in summary.
> * Change "noexcept(noexcept(...))" to noexcept(is_noexcept_invocable<>)
> * Change "requires requires {...}" to "require is_invocable_v<>"
> * Adjust more whitespace.
> * Add more tests.
>
> Changes in v5:
> * Switch back to decltype(auto) returns
> * Adjust tests to verify that calls treat <f> and all bound args
> as const.
> * Use "&&", "||" and "!" in preference to "and", "or" and "not"
> in deference to users "--no-operator-names".
> * Conform to conditional block format conventions, remove
> redundant () on noexcept predicates.
> * Return a static lambda in not_fn<f>()
>
> 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.
>
> There is no need to change libstdc++-v3/src/c++23/std.cc.in
> because existing exports: "using std::bind_front;" etc.
> exports 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
> * 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
> ---
>
LGTM to me, only two minor comments:
* indentation after template (purery stylistics, but we want to match it)
* moving the conditions in the requires of bind_front/bind_front as static
assert
in the body. I have double checked with standard, and they are specified
by Mandates.
> libstdc++-v3/include/bits/version.def | 12 +
> libstdc++-v3/include/bits/version.h | 21 +-
> libstdc++-v3/include/std/functional | 143 +++++++++-
> .../20_util/function_objects/bind_back/1.cc | 16 +-
> .../function_objects/bind_back/nttp.cc | 258 +++++++++++++++++
> .../20_util/function_objects/bind_front/1.cc | 16 +-
> .../function_objects/bind_front/nttp.cc | 260 ++++++++++++++++++
> .../20_util/function_objects/not_fn/nttp.cc | 98 +++++++
> .../20_util/headers/functional/synopsis.cc | 21 ++
> 9 files changed, 823 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_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 dbe2cb8f175..81b6ec20ee5 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 7bb6016df68..1160ea0a00a 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..637e2106706 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
> @@ -87,7 +88,8 @@
> # include <bits/ranges_cmp.h> // std::identity, ranges::equal_to etc.
> # include <compare>
> #endif
> -#if __glibcxx_move_only_function || __glibcxx_copyable_function ||
> __glibcxx_function_ref
> +#if __glibcxx_move_only_function || __glibcxx_copyable_function || \
> + __glibcxx_function_ref
> # include <bits/funcwrap.h>
> #endif
>
> @@ -921,6 +923,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> std::forward<_BoundArgs>(__args)...);
> }
>
> +#if defined(__cpp_lib_bind_front) || defined(__cpp_lib_bind_front)
> + template <auto __fn>
>
We do have two spaces of indent for things following template head,
see rest of the file.
> + struct _Bind_fn_t
> + {
> + using _Fn = const decltype(__fn)&;
> + template <typename... _Args>
> + 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
>
> template<typename _Fd, typename... _BoundArgs>
> @@ -940,7 +956,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 +1065,51 @@ _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> && ...) &&
> + (is_move_constructible_v<decay_t<_BindArgs>> && ...)
>
These are Mandates not Constraints, in the wording, so they should be
static
asserts in the body: see https://eel.is/c++draft/func.bind.partial.
> + {
> + using _Fn = decltype(__fn);
> + 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))]
> + <typename _Self, typename... _CallArgs>
> + (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
> @@ -1118,6 +1179,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> && ...) &&
> + (is_move_constructible_v<decay_t<_BindArgs>> && ...)
>
Similar comments on changing this to static assert.
> + {
> + using _Fn = decltype(__fn);
> + 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))]
> + <typename _Self, typename... _CallArgs>
> + (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
> @@ -1218,7 +1325,37 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> {
> return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0};
> }
> -#endif
> +
> +#if __cpp_lib_not_fn >= 202306
> +
> + /** 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> || is_member_pointer_v<_Fn>)
> + static_assert(__fn != nullptr);
> + return []<typename... _Args>(_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 >= 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..f3559b7f960 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,22 +149,22 @@ 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
> 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 00000000000..4dff909a387
> --- /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<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()(int) {} };
> + constexpr F f{};
> +
> + // 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, 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<f>();
> + 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<f>(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<f>(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<f>(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<f>(); // call wrapper has no bound arg
> + using G0 = decltype(g0);
> + static_assert(is_invocable_r_v<void*, G0&, void*, int>);
> + static_assert(is_invocable_r_v<void*, G0&, void*, int&>);
> + static_assert(is_invocable_r_v<void*, G0&, void*, const int&>);
> + static_assert(is_invocable_r_v<void*, const G0&, void*, int>);
> + static_assert(is_invocable_r_v<void*, G0&&, void*, int>);
> + void* p0 = static_cast<G0&&>(g0)(vp, i);
> + VERIFY( p0 == vp );
> +
> + auto g1 = bind_back<f>(i); // call wrapper has bound arg of type int
> + using G1 = decltype(g1);
> + static_assert(!is_invocable_r_v<int&, G1&, void*>);
> + 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 );
> +
> + auto g2 = bind_back<f>(std::ref(i)); // bound arg of type int&
> + using G2 = decltype(g2);
> + static_assert(is_invocable_r_v<void*, G2&, void*>);
> + static_assert(is_invocable_r_v<void*, G2&&, void*>);
> + static_assert(is_invocable_r_v<void*, const G2&, void*>);
> + static_assert(is_invocable_r_v<void*, const G2&&, void*>);
> + void* p2 = g2(vp);
> + VERIFY( p2 == vp );
> + p2 = static_cast<G2&&>(g2)(vp);
> + VERIFY( p2 == vp );
> + 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);
> + 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*>);
> +}
> +
> +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<f>(i); // call wrapper has bound arg of type int
> + using G1 = decltype(g1);
> + static_assert(is_invocable_r_v<void*, G1&, void*>);
> + 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 );
> +}
> +
> +
> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
> +
> +consteval bool
> +test04()
> +{
> + constexpr auto g = bind_back<f>();
> + VERIFY( std::is_empty_v<decltype(g)> );
> + VERIFY(g(1, 2, 3) == 1 + 2*2 + 3*3 );
> + constexpr auto g1 = bind_back<f>(1);
> + VERIFY(g1(2, 3) == 3*1 + 1*2 + 2*3 );
> + VERIFY(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 );
> + constexpr auto g2 = bind_back<f>(1, 2);
> + VERIFY(g2(3) == 2*1 + 3*2 + 1*3 );
> + VERIFY(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 );
> + constexpr auto g3 = bind_back<f>(1, 2, 3);
> + VERIFY(g3() == 1 + 2*2 + 3*3);
> + VERIFY(bind_back<g2>(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<f5>(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/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..1287004460e
> --- /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<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()(int) {} };
> + constexpr F f{};
> +
> + // 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, 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<f>();
> + 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<f>(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<f>(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<f>(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<f>(); // call wrapper has no bound arg
> + using G0 = decltype(g0);
> + static_assert(is_invocable_r_v<void*, G0&, int, void*>);
> + static_assert(is_invocable_r_v<void*, G0&, int&, void*>);
> + static_assert(is_invocable_r_v<void*, G0&, const int&, void*>);
> + static_assert(is_invocable_r_v<void*, const G0&, int, void*>);
> + static_assert(is_invocable_r_v<void*, G0&&, int, void*>);
> + void* p0 = static_cast<G0&&>(g0)(i, vp);
> + VERIFY( p0 == vp );
> +
> + auto g1 = bind_front<f>(i); // call wrapper has bound arg of type int
> + using G1 = decltype(g1);
> + static_assert(!is_invocable_r_v<int&, G1&, void*>);
> + 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 );
> +
> + auto g2 = bind_front<f>(std::ref(i)); // bound arg of type int&
> + using G2 = decltype(g2);
> + static_assert(is_invocable_r_v<void*, G2&, void*>);
> + static_assert(is_invocable_r_v<void*, G2&&, void*>);
> + static_assert(is_invocable_r_v<void*, const G2&, void*>);
> + static_assert(is_invocable_r_v<void*, const G2&&, void*>);
> + void* p2 = g2(vp);
> + VERIFY( p2 == vp );
> + p2 = static_cast<G2&&>(g2)(vp);
> + VERIFY( p2 == vp );
> + 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);
> + 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*>);
> +}
> +
> +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<f>(i); // call wrapper has bound arg of type int
> + using G1 = decltype(g1);
> + static_assert(is_invocable_r_v<void*, G1&, void*>);
> + 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 );
> +}
> +
> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
> +
> +consteval bool
> +test04()
> +{
> + constexpr auto g = bind_front<f>();
> + VERIFY( std::is_empty_v<decltype(g)> );
> + VERIFY( g(1, 2, 3) == 1 + 2*2 + 3*3 );
> + constexpr auto g1 = bind_front<f>(1);
> + VERIFY( g1(2, 3) == 1 + 2*2 + 3*3 );
> + VERIFY( bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 );
> + constexpr auto g2 = bind_front<f>(1, 2);
> + VERIFY( g2(3) == 1 + 2*2 + 3*3 );
> + VERIFY( bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 );
> + constexpr auto g3 = bind_front<f>(1, 2, 3);
> + VERIFY( g3() == 1 + 2*2 + 3*3 );
> + VERIFY(bind_front<g2>(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<f5>(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/not_fn/nttp.cc
> b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
> new file mode 100644
> index 00000000000..f0b07e7acd8
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
> @@ -0,0 +1,98 @@
> +// 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( 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()(const abstract&) const { return false; } };
> + constexpr F f;
> + constexpr 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!() const noexcept(false) { return *this; }
> + };
> + struct F {
> + Boolean operator()() const { return {}; }
> + };
> + const F f;
> + const auto notf = std::not_fn<f>();
> + using NotF = decltype(notf);
> + 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);
> + 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250730/5cc3f4d6/attachment-0001.htm>
More information about the Libstdc++
mailing list