[PATCHv2] libstdc++: Add NTTP bind_front, -back, not_fn (P2714) [PR119744]

Nathan Myers ncm@cantrip.org
Sat Jul 19 00:39:55 GMT 2025


On 7/18/25 07:21, Tomasz Kaminski wrote:
> On Fri, Jul 18, 2025 at 12:52 PM Nathan Myers <ncm@cantrip.org 
> <mailto:ncm@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
> 
> Similary bind_back, could use __Fn_t.

Thank you, I will pursue this. Death to all unnecessary tuples.

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

Thank you. This probably accounts for most of the test result differences.

>     +       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)...))

Thank you, this is much cleaner. Not sure whether the requires
clause actually does anything useful for users here...

Is there a policy on when "std::" annotation is still needed?
Maybe in places where there is a possibility of ADL?
Presumably it is faster to compile even when the wrong function
cannot be chosen.

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



More information about the Libstdc++ mailing list