On Tue, Jul 22, 2025 at 11:28 AM Tomasz Kaminski <tkami...@redhat.com>
wrote:

>
>
> On Tue, Jul 22, 2025 at 9:22 AM Nathan Myers <n...@cantrip.org> wrote:
>
>> This should be close to ready. However, std::is_invocable
>> and noexcept still fail oddly applied to the not_fp result.
>> The remaining failing test cases in */nttp.cc, commented
>> out, need careful examination to see whether they should
>> be expecting different results given that the argument
>> function object is necessarily const.
>>
>> Changes in v4:
>>  * For the no-bound-arguments case, bind_front and bind_back both
>>   return a zero-size _BindFn_t with static operator().
>>  * For the normal case, the lambda functions returned are declared
>>   to yield std::invoke_result_t<> instead of decltype(auto), which
>>   produces different test outcomes.
>>
>> Changes in v3:
>>  * NTTP functions bind_front, bind_back are self-contained.
>>  * No tuples: bind_front and bind_back return a simple lambda.
>>  * bind_front, _back with no arguments simply return the template
>>   parameter function.
>>  * Forwarded-argument passing is disciplined.
>>  * NTTP not_fn uses a helper struct with static op().
>>  * Many more of tests that pass non-NTTP versions also pass
>>
>> Add non-type template parameter function-object/-pointer argument
>> versions of bind_front, bind_back, and not_fn.
>>
>> libstdc++-v3/ChangeLog:
>>         PR libstdc++/119744
>>         * include/bits/version.def: Redefine __cpp_lib_bind_front etc.
>>         * include/bits/version.h: Ditto.
>>         * include/std/functional: Add new bind_front etc. overloads
>>         * testsuite/20_util/function_objects/bind_back/1.cc
>>         * testsuite/20_util/function_objects/bind_back/nttp.cc
>>         * testsuite/20_util/function_objects/bind_front/1.cc
>>         * testsuite/20_util/function_objects/bind_front/nttp.cc
>>         * testsuite/20_util/function_objects/not_fn/nttp.cc
>>         * testsuite/20_util/headers/functional/synopsis.cc
>> ---
>>  libstdc++-v3/include/bits/version.def         |  12 +
>>  libstdc++-v3/include/bits/version.h           |  21 +-
>>  libstdc++-v3/include/std/functional           | 159 ++++++++++++-
>>  .../20_util/function_objects/bind_back/1.cc   |  22 +-
>>  .../function_objects/bind_back/nttp.cc        | 224 ++++++++++++++++++
>>  .../20_util/function_objects/bind_front/1.cc  |  16 +-
>>  .../function_objects/bind_front/nttp.cc       | 218 +++++++++++++++++
>>  .../20_util/function_objects/not_fn/nttp.cc   | 113 +++++++++
>>  .../20_util/headers/functional/synopsis.cc    |  21 ++
>>  9 files changed, 781 insertions(+), 25 deletions(-)
>>  create mode 100644
>> libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
>>  create mode 100644
>> libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
>>  create mode 100644
>> libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
>>
>> diff --git a/libstdc++-v3/include/bits/version.def
>> b/libstdc++-v3/include/bits/version.def
>> index 2f70a529927..7909a7b194a 100644
>> --- a/libstdc++-v3/include/bits/version.def
>> +++ b/libstdc++-v3/include/bits/version.def
>> @@ -463,6 +463,10 @@ ftms = {
>>
>>  ftms = {
>>    name = not_fn;
>> +  values = {
>> +    v = 202306;
>> +    cxxmin = 26;
>> +  };
>>    values = {
>>      v = 201603;
>>      cxxmin = 17;
>> @@ -776,6 +780,10 @@ ftms = {
>>
>>  ftms = {
>>    name = bind_front;
>> +  values = {
>> +    v = 202306;
>> +    cxxmin = 26;
>> +  };
>>    values = {
>>      v = 201907;
>>      cxxmin = 20;
>> @@ -784,6 +792,10 @@ ftms = {
>>
>>  ftms = {
>>    name = bind_back;
>> +  values = {
>> +    v = 202306;
>> +    cxxmin = 26;
>> +  };
>>    values = {
>>      v = 202202;
>>      cxxmin = 23;
>> diff --git a/libstdc++-v3/include/bits/version.h
>> b/libstdc++-v3/include/bits/version.h
>> index 8e0ae682251..9721d1d23fe 100644
>> --- a/libstdc++-v3/include/bits/version.h
>> +++ b/libstdc++-v3/include/bits/version.h
>> @@ -511,7 +511,12 @@
>>  #undef __glibcxx_want_make_from_tuple
>>
>>  #if !defined(__cpp_lib_not_fn)
>> -# if (__cplusplus >= 201703L)
>> +# if (__cplusplus >  202302L)
>> +#  define __glibcxx_not_fn 202306L
>> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn)
>> +#   define __cpp_lib_not_fn 202306L
>> +#  endif
>> +# elif (__cplusplus >= 201703L)
>>  #  define __glibcxx_not_fn 201603L
>>  #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn)
>>  #   define __cpp_lib_not_fn 201603L
>> @@ -866,7 +871,12 @@
>>  #undef __glibcxx_want_atomic_value_initialization
>>
>>  #if !defined(__cpp_lib_bind_front)
>> -# if (__cplusplus >= 202002L)
>> +# if (__cplusplus >  202302L)
>> +#  define __glibcxx_bind_front 202306L
>> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front)
>> +#   define __cpp_lib_bind_front 202306L
>> +#  endif
>> +# elif (__cplusplus >= 202002L)
>>  #  define __glibcxx_bind_front 201907L
>>  #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front)
>>  #   define __cpp_lib_bind_front 201907L
>> @@ -876,7 +886,12 @@
>>  #undef __glibcxx_want_bind_front
>>
>>  #if !defined(__cpp_lib_bind_back)
>> -# if (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter)
>> +# if (__cplusplus >  202302L)
>> +#  define __glibcxx_bind_back 202306L
>> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back)
>> +#   define __cpp_lib_bind_back 202306L
>> +#  endif
>> +# elif (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter)
>>  #  define __glibcxx_bind_back 202202L
>>  #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back)
>>  #   define __cpp_lib_bind_back 202202L
>> diff --git a/libstdc++-v3/include/std/functional
>> b/libstdc++-v3/include/std/functional
>> index 307bcb95bcc..03375e2a32a 100644
>> --- a/libstdc++-v3/include/std/functional
>> +++ b/libstdc++-v3/include/std/functional
>> @@ -71,6 +71,7 @@
>>  #include <type_traits>
>>  #include <bits/functional_hash.h>
>>  #include <bits/invoke.h>
>> +#include <bits/move.h>
>>  #include <bits/refwrap.h>      // std::reference_wrapper and
>> _Mem_fn_traits
>>  #if _GLIBCXX_HOSTED
>>  # include <bits/std_function.h>        // std::function
>> @@ -921,6 +922,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>> std::forward<_BoundArgs>(__args)...);
>>      }
>>
>> +#if __cpp_lib_bind_front >= 202306 || __cpp_lib_bind_front >= 202306
>>
> This should say  __cpp_lib_bind_front || __cpp_lib_bind_back
>
>> +  template <auto __fn>
>> +    struct _BindFn_t
>> +    {
>> +      using _Fn = decltype(__fn);
>> +
>> +      template <typename... _Args>
>> +      constexpr static auto
>> +      operator()(_Args... __args)
>> +       noexcept(is_nothrow_invocable_v<_Fn, _Args...>)
>> +       -> invoke_result_t<             _Fn, _Args...>
>>
> Once you check is_invocable_v this could be simplyu decltype(auto).
>
>> +       requires (is_invocable_v<       _Fn, _Args...>)
>> +      { return invoke(__fn, forward<_Args>(__args)...); }
>> +
>> +      void operator=(this auto&&, _BindFn_t&&) = delete;
>> +    };
>> +#endif
>> +
>>  #ifdef __cpp_lib_bind_front // C++ >= 20
>>
>>    template<typename _Fd, typename... _BoundArgs>
>> @@ -940,7 +959,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>           _M_bound_args(std::forward<_Args>(__args)...)
>>         { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); }
>>
>> -#if __cpp_explicit_this_parameter
>> +#ifdef __cpp_explicit_this_parameter
>>        template<typename _Self, typename... _CallArgs>
>>         constexpr
>>         invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self,
>> _BoundArgs>..., _CallArgs...>
>> @@ -1049,6 +1068,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>        return _Bind_front_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn),
>>                                           std::forward<_Args>(__args)...);
>>      }
>> +
>> +#if __cpp_lib_bind_front >= 202306
>> +
>> +  /** Create call wrapper by partial application of arguments to
>> function.
>> +   *
>> +   * The result of `std::bind_front<fn>(bind_args...)` is a function
>> object
>> +   * that stores the bound arguments, `bind_args...`. When that function
>> +   * object is invoked with `call_args...` it returns the result of
>> calling
>> +   * `fn(bind_args..., call_args...)`.
>> +   *
>> +   *  @since C++26
>> +   */
>> +  template<auto __fn, typename... _BindArgs>
>> +  constexpr decltype(auto)
>> +  bind_front(_BindArgs&&... __bind_args)
>> +    noexcept(__and_<is_nothrow_constructible<_BindArgs>...>::value)
>> +    requires (
>> +      (is_constructible_v<decay_t<_BindArgs>, _BindArgs> and ...) and
>> +      (is_move_constructible_v<decay_t<_BindArgs>> and ...))
>> +  {
>> +    using _Fn = decltype(__fn);
>> +    if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) {
>>
>
>
>> +      static_assert(__fn != nullptr);
>> +    }
>> +    if constexpr (sizeof...(_BindArgs) == 0) {
>> +      return _BindFn_t<__fn>{};
>> +    } else {
>>
> The formatting style is a bit different in libstdc++ and braces are in new
> line and intendent, something
> like:
> if constexpr (sizeof...(_BindArgs) == 0)
> // no braces, 2 space indent from if, same as brace would be
>   return _BindFn_t<__fn>{};
> // else at same indentation as if
> else
> // two space indent
>   {
> // additional two space vs {, and 4 from else in total
>      // Capture arguments in a lambda and return that.
> +      // Capture arguments in a lambda and return that.
> +      return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
> +       <typename _Self, typename... _CallArgs>
> +       (this _Self&&, _CallArgs&&... __call_args)
> +       noexcept(is_nothrow_invocable_v<
> +         _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>)
>
In any of this check, _Fn should be replaced with const _Fn& type, because
we are always referring to template parameter, which is const lvalue.

> +       -> invoke_result_t<
> +         _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>
> Once we checked is_invocable_v this could be simply decltype(auto),
> reducing the number of instantiation. You can use -> decltype(auto) in
> lambda
>
>> +       requires (is_invocable_v<
>> +         _Fn, __like_t<_Self, decay_t<_BindArgs>>..., _CallArgs...>)
>>
> No parenthesis here.
>
>> +      {
>> +       return invoke(__fn,
>> +         forward_like<_Self>(__bound_args)...,
>> +         forward<_CallArgs>(__call_args)...);
>> +      };
>> +    }
>> +  }
>> +
>> +#endif // __cpp_lib_bind_front  // C++26
>>  #endif // __cpp_lib_bind_front
>>
>>  #ifdef __cpp_lib_bind_back // C++ >= 23
>> @@ -1118,6 +1183,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>        return _Bind_back_t<_Fn, _Args...>(0, std::forward<_Fn>(__fn),
>>                                          std::forward<_Args>(__args)...);
>>      }
>> +
>> +#if __cpp_lib_bind_back >= 202306
>> +
>> +  /** Create call wrapper by partial application of arguments to
>> function.
>> +   *
>> +   * The result of `std::bind_back<fn>(bind_args...)` is a function
>> object
>> +   * that stores the arguments, `bind_args...`. When that function object
>> +   * is invoked with `call_args...` it returns the result of calling
>> +   * `fn(call_args..., bind_args...)`.
>> +   *
>> +   *  @since C++26
>> +   */
>> +  template<auto __fn, typename... _BindArgs>
>> +  constexpr decltype(auto)
>> +  bind_back(_BindArgs&&... __bind_args)
>> +    noexcept(__and_<is_nothrow_constructible<_BindArgs>...>::value)
>> +    requires (
>> +      (is_constructible_v<decay_t<_BindArgs>, _BindArgs> and ...) and
>> +      (is_move_constructible_v<decay_t<_BindArgs>> and ...))
>> +  {
>>
> Same comments as above.
>
>> +    using _Fn = decltype(__fn);
>> +    if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) {
>> +      static_assert(__fn != nullptr);
>> +    }
>> +    if constexpr (sizeof...(_BindArgs) == 0) {
>> +      return _BindFn_t<__fn>{};
>> +    } else {
>> +      // Capture arguments in a lambda and return that.
>> +      return [... __bound_args(std::forward<_BindArgs>(__bind_args))]
>> +       <typename _Self, typename... _CallArgs>
>> +       (this _Self&&, _CallArgs&&... __call_args)
>> +       noexcept(is_nothrow_invocable_v<
>> +         _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>)
>> +       -> invoke_result_t<
>> +         _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>
>> +       requires (is_invocable_v<
>> +         _Fn, _CallArgs..., __like_t<_Self, decay_t<_BindArgs>>...>)
>>
> +      {
>> +       return invoke(__fn,
>> +         forward<_CallArgs>(__call_args)...,
>> +         forward_like<_Self>(__bound_args)...);
>> +      };
>> +    }
>> +  }
>> +
>> +#endif // __cpp_lib_bind_back  // C++26, nttp
>>  #endif // __cpp_lib_bind_back
>>
>>  #if __cplusplus >= 201402L
>> @@ -1212,13 +1323,55 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>     */
>>    template<typename _Fn>
>>      _GLIBCXX20_CONSTEXPR
>> -    inline auto
>> +    inline decltype(auto)
>>
> `auto` here is fine, as we are returning a prvalue of function wrapper
> here.
>
>>      not_fn(_Fn&& __fn)
>>      noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>,
>> _Fn&&>::value)
>>      {
>>        return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0};
>>      }
>> -#endif
>> +
>> +#if __cpp_lib_not_fn >= 202306
>> +
>> +  template<auto __fn>
>> +  struct _Not_fn_nttp
>> +  {
>> +    template<typename... _Args>
>> +      constexpr static decltype(auto)
>> +      operator()(_Args&&... __args)
>> +       noexcept( noexcept(
>> +              not std::invoke(__fn, std::forward<_Args>(__args)...) ))
>>
> There is a gcc/clang -fno-operator-names configuration option to disable
> not as alternative keyword.
> You should use ! here,
>
>> +       requires requires {
>> +              not std::invoke(__fn, std::forward<_Args>(__args)...); }
>> +      {
>> +       return not std::invoke(__fn, std::forward<_Args>(__args)...);
>> +      }
>> +  };
>> +
>> +  /** Wrap a function type to create a function object that negates its
>> result.
>> +   *
>> +   * The function template `std::not_fn` creates a "forwarding call
>> wrapper",
>> +   * which is a function object that when called forwards its arguments
>> to
>> +   * its invocable template argument.
>> +   *
>> +   * The result of invoking the wrapper is the negation (using `!`) of
>> +   * the wrapped function object.
>> +   *
>> +   *  @ingroup functors
>> +   *  @since C++26
>> +   */
>> +  template<auto __fn>
>> +  constexpr decltype(auto)
>> +  not_fn() noexcept
>> +  {
>> +    using _Fn = decltype(__fn);
>> +    if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) {
>> +      static_assert(__fn != nullptr);
>> +    }
>> +    return _Not_fn_nttp<__fn>{};
>> +  }
>> +
>> +#endif // __cpp_lib_not_fn >= 202306
>> +#endif // __cpp_lib_not_fn
>>
>>  #if __cplusplus >= 201703L
>>    // Searchers
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
>> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
>> index c31d3228815..feedead477b 100644
>> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
>> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
>> @@ -149,23 +149,23 @@ test03()
>>    static_assert(is_invocable_r_v<void*, const G4&&>);
>>  }
>>
>> -constexpr int f(int i, int j, int k) { return i + 2*(j + k); }
>> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
>>
>> -constexpr bool
>> +constexpr int
>>  test04()
>>  {
>>    auto g = bind_back(f);
>> -  VERIFY( g(1, 2, 3) == 1 + 2*(2 + 3) );
>> +  if (!( g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
>>
> I do not think you need to replace VERIFY here,
> the macro works in they way that VERIFY will fail to be
> core-constant expressions if condition is not met, but
> will compile fine otherwise. So restore them.
>
>>    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;
>>
> After using VERIFY, jus return true here.
>
>>  }
>>
>>  int
>> @@ -174,5 +174,5 @@ main()
>>    test01();
>>    test02();
>>    test03();
>> -  static_assert(test04());
>> +  static_assert(test04() == 0);
>>
> And this will be static_assert(test04());
>
>>  }
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
>> b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
>> new file mode 100644
>> index 00000000000..23e5fe850c2
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
>> @@ -0,0 +1,224 @@
>> +// { dg-do run { target c++26 } }
>> +// { dg-add-options no_pch }
>> +
>> +// Test NTTP bind_back<f>(Args...), P2714
>> +
>> +// #define TRY
>> +// #define TRY_CT  // turn on static asserts
>> +
>> +#include <functional>
>> +
>> +#ifndef __cpp_lib_bind_back
>> +# error "Feature test macro for bind_back is missing in <functional>"
>> +#elif __cpp_lib_bind_back < 202306L
>> +# error "Feature test macro for bind_back has wrong value in
>> <functional>"
>> +#endif
>> +
>> +#include <testsuite_hooks.h>
>> +
>> +using std::bind_back;
>> +using std::is_same_v;
>> +using std::is_invocable_v;
>> +using std::is_invocable_r_v;
>> +
>> +void
>> +test01()
>> +{
>> +  struct F { void operator()() {} };
>> +  constexpr F f{};
>> +
>> +  // [The following differ from the non-NTTP version of bind_back.]
>>
> This is a bug in our implementation of bind_back/front. The standard is
> clear here:
> > BoundArgs is a pack that denotes decay_t<Args>...,
> https://eel.is/c++draft/func.bind.partial#1.4:
> Could you create a bug in Bugzilla for this?
>
>> +  // Arguments should be decayed:
>> +#ifdef TRY_CT
>> +  // [The following differs from the non-NTTP version of bind_back.]
>> +  static_assert(std::is_same_v<
>> +      decltype(bind_back<f>(std::declval<int>())),
>> +      decltype(bind_back<f>(std::declval<int&>()))
>> +      >);
>> +  static_assert(std::is_same_v<
>> +      decltype(bind_back<f>(std::declval<int>())),
>> +      decltype(bind_back<f>(std::declval<const int&>()))
>> +      >);
>> +#endif
>> +
>> +  // Reference wrappers should be handled:
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_back<f>(std::declval<int&>())),
>> +      decltype(bind_back<f>(std::ref(std::declval<int&>())))
>> +      >);
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_back<f>(std::declval<const int&>())),
>> +      decltype(bind_back<f>(std::cref(std::declval<int&>())))
>> +      >);
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_back<f>(std::ref(std::declval<int&>()))),
>> +      decltype(bind_back<f>(std::cref(std::declval<int&>())))
>> +      >);
>> +}
>> +
>> +void
>> +test02()
>> +{
>> +  struct quals
>> +  {
>> +    bool as_const;
>> +    bool as_lvalue;
>> +  };
>> +
>> +  struct F
>> +  {
>> +    quals operator()(int) & { return { false, true }; }
>> +    quals operator()(int) const & { return { true, true }; }
>> +    quals operator()(int) && { return { false, false }; }
>> +    quals operator()(int) const && { return { true, false }; }
>> +  };
>> +
>> +  {
>> +    constexpr F f;
>> +    auto g = bind_back<f>();
>> +    const auto& cg = g;
>> +    quals q;
>> +
>> +    // Constness and value category forwarded to the target object?
>>
> I have mentioned that in the e-mail, but because f is NTTP, it will be
> always
> considered to be F const& reference, regardless of the qualifier of the
> object.
> You should instead adjust the type of argument, as I have mentioned here:
> https://gcc.gnu.org/pipermail/libstdc++/2025-July/062426.html
>
> +#if TRY
>> +    // [The following differs from the non-NTTP version of bind_back.]
>>
> +    q = g(0);
>> +    VERIFY( ! q.as_const && q.as_lvalue );
>> +    q = std::move(g)(0);
>> +    VERIFY( ! q.as_const && ! q.as_lvalue );
>> +    q = cg(0);
>> +    VERIFY( q.as_const && q.as_lvalue );
>> +    q = std::move(cg)(0);
>> +    VERIFY( q.as_const && ! q.as_lvalue );
>> +#endif
>> +  }
>> +  {
>> +    constexpr F f;
>> +    auto g = bind_back<f>(0);
>> +    const auto& cg = g;
>> +    quals q;
>> +
>> +    // Constness and value category forwarded to the target object?
>> +#ifdef TRY
>> +    // [The following differs from the non-NTTP version of bind_back.]
>> +    q = g();
>> +    VERIFY( ! q.as_const && q.as_lvalue );
>> +    q = std::move(g)();
>> +    VERIFY( ! q.as_const && ! q.as_lvalue );
>> +    q = cg();
>> +    VERIFY( q.as_const && q.as_lvalue );
>> +    q = std::move(cg)();
>> +    VERIFY( q.as_const && ! q.as_lvalue );
>> +#endif
>> +  }
>> +}
>> +
>> +void
>> +test03()
>> +{
>> +  struct F
>> +  {
>> +    int& operator()(void*, int& i) { return i; }
>> +    void* operator()(void* p, int) const { return p; }
>> +  };
>> +
>> +  int i = 5;
>> +  void* vp = &vp; // arbitrary void* value
>> +
>> +  constexpr F f;
>> +  auto g1 = bind_back<f>(i); // call wrapper has bound arg of type int
>> +  using G1 = decltype(g1);
>> +  // Invoking G1& will pass g1's bound arg as int&, so calls first
>> overload:
>> +#ifdef TRY_CT
>> +  // [The following differs from the non-NTTP version of bind_back.]
>> +  static_assert(!is_invocable_r_v<int&, G1&, void*>);
>>
> I have explained the diffs here:
> https://gcc.gnu.org/pipermail/libstdc++/2025-July/062426.html
>
>> +#endif
>> +
>> +  // Invoking const G1& or G&& calls second overload:
>> +  static_assert(is_invocable_r_v<void*, const G1&, void*>);
>> +  static_assert(is_invocable_r_v<void*, G1&&, void*>);
>> +  void* p1 = static_cast<G1&&>(g1)(vp);
>> +  VERIFY( p1 == vp );
>> +
>> +  // And can call first overload on const G6:
>> +  auto g2 = bind_back<f>(std::ref(i)); // bound arg of type int&
>> +  using G2 = decltype(g2);
>> +  // Bound arg always forwarded as int& even from G2&& or const G2&
>> +#ifdef TRY_CT
>> +  // [The following differ from the non-NTTP version of bind_back.]
>> +  static_assert(is_invocable_r_v<int&, G2&, void*>);
>> +  static_assert(is_invocable_r_v<int&, G2&&, void*>);
>> +#endif
>> +
>> +  // But cannot call first overload on const G2:
>> +#ifdef TRY_CT
>> +  // [The following differs from the non-NTTP version of bind_back.]
>> +  static_assert(is_invocable_r_v<void*, const G2&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G2&&, void*>);
>> +  void* i2 = g2(vp);
>> +  VERIFY( &i2 == &i );
>> +  void* i2r = static_cast<G2&&>(g2)(vp);
>> +  VERIFY( iv2r == &i );
>> +  void* p2 = const_cast<const G2&>(g2)(vp);
>> +  VERIFY( p2 == vp );
>> +#endif
>> +
>> +  auto g3 = bind_back<f>(std::cref(i)); // bound arg of type const int&
>> +  using G3 = decltype(g3);
>> +  // Bound arg always forwarded as const int& so can only call second
>> overload:
>> +  static_assert(is_invocable_r_v<void*, G3&, void*>);
>> +  static_assert(is_invocable_r_v<void*, G3&&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G3&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G3&&, void*>);
>> +
>> +#ifdef TRY_CT
>> +  // [The following differ from the non-NTTP version of bind_back.]
>> +  // auto g4 = bind_back<g2>(nullptr);
>> +  // using G4 = decltype(g4);
>> +  // static_assert(is_invocable_r_v<int&, G4&>);
>> +  // static_assert(is_invocable_r_v<int&, G4&&>);
>> +  // static_assert(is_invocable_r_v<void*, const G4&>);
>> +  // static_assert(is_invocable_r_v<void*, const G4&&>);
>> +#endif
>> +}
>> +
>> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
>> +
>> +consteval int
>> +test04()
>> +{
>> +  constexpr auto g = bind_back<f>();
>> +  VERIFY( std::is_empty_v<decltype(g)> );
>>
> +  if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
>>
> This could use VERIFY.
>
>> +  constexpr auto g1 = bind_back<f>(1);
>> +  if (!(g1(2, 3) == 3*1 + 1*2 + 2*3 )) return 6;
>> +  if (!(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 )) return 5;
>> +  constexpr auto g2 = bind_back<f>(1, 2);
>> +  if (!(g2(3) == 2*1 + 3*2 + 1*3 )) return 4;
>> +  if (!(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 )) return 3;
>> +  constexpr auto g3 = bind_back<f>(1, 2, 3);
>> +  if (!(g3() == 1 + 2*2 + 3*3)) return 2;
>> +  if (!(bind_back<g2>(3)() == 1*2 + 2*3 + 3*1 )) return 1;
>> +  return 0;
>> +}
>> +
>> +template <auto nnfp, auto nfp>
>> +void test05() {
>> +  VERIFY(bind_back<nnfp>(1)(2, 3) == 3*1 + 1*2 + 2*3);
>> +#ifdef TRY_CT
>> +  // [Fails to sandbox the static_assert(fp) in bind_back<fp>():]
>> +  VERIFY(!requires { bind_back<nfp>(1); });
>> +#endif
>> +}
>> +
>> +int
>> +main()
>> +{
>> +  test01();
>> +  test02();
>> +  test03();
>> +  static_assert(test04() == 0);
>> +  constexpr int (*nnfp)(int, int, int) = f;
>> +  constexpr int (*nfp)(int, int, int) = nullptr;
>> +  test05<nnfp, nfp>();
>> +}
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
>> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
>> index 57482c52263..b038889fbb4 100644
>> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
>> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
>>
> Same comments as for bind_back.
>
>> @@ -149,22 +149,22 @@ test03()
>>    static_assert(is_invocable_r_v<void*, const G4&&>);
>>  }
>>
>> -int f(int i, int j, int k) { return i + j + k; }
>> +int f(int i, int j, int k) { return i + 2*j + 3*k; }
>>
>>  void
>>  test04()
>>  {
>>    auto g = bind_front(f);
>> -  VERIFY( g(1, 2, 3) == 6 );
>> +  VERIFY( g(1, 2, 3) == 14 );
>>    auto g1 = bind_front(f, 1);
>> -  VERIFY( g1(2, 3) == 6 );
>> -  VERIFY( bind_front(g, 1)(2, 3) == 6 );
>> +  VERIFY( g1(2, 3) == 14 );
>> +  VERIFY( bind_front(g, 1)(2, 3) == 14 );
>>    auto g2 = bind_front(f, 1, 2);
>> -  VERIFY( g2(3) == 6 );
>> -  VERIFY( bind_front(g1, 2)(3) == 6 );
>> +  VERIFY( g2(3) == 14 );
>> +  VERIFY( bind_front(g1, 2)(3) == 14 );
>>    auto g3 = bind_front(f, 1, 2, 3);
>> -  VERIFY( g3() == 6 );
>> -  VERIFY( bind_front(g2, 3)() == 6 );
>> +  VERIFY( g3() == 14 );
>> +  VERIFY( bind_front(g2, 3)() == 14 );
>>  }
>>
>>  int
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
>> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
>> new file mode 100644
>> index 00000000000..c06b153ee28
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
>> @@ -0,0 +1,218 @@
>> +// { dg-do run { target c++26 } }
>> +// { dg-add-options no_pch }
>> +
>> +// Test NTTP bind_front<f>(Args...), P2714
>> +
>> +// #define TRY
>> +// #define TRY_CT // turn on static asserts
>> +
>> +#include <functional>
>> +
>> +#ifndef __cpp_lib_bind_front
>> +# error "Feature test macro for bind_front is missing in <functional>"
>> +#elif __cpp_lib_bind_front < 201902L
>> +# error "Feature test macro for bind_front has wrong value in
>> <functional>"
>> +#endif
>> +
>> +#include <testsuite_hooks.h>
>> +
>> +using std::bind_front;
>> +using std::is_same_v;
>> +using std::is_invocable_v;
>> +using std::is_invocable_r_v;
>> +
>> +void
>> +test01()
>> +{
>> +  struct F { void operator()() {} };
>> +  constexpr F f{};
>> +
>> +  // Arguments should be decayed:
>> +#ifdef TRY_CT
>> +  // [The following differ from the non-NTTP version of bind_front.]
>> +  static_assert(std::is_same_v<
>> +      decltype(bind_front<f>(std::declval<int>())),
>> +      decltype(bind_front<f>(std::declval<int&>()))
>> +      >);
>> +  static_assert(std::is_same_v<
>> +      decltype(bind_front<f>(std::declval<int>())),
>> +      decltype(bind_front<f>(std::declval<const int&>()))
>> +      >);
>> +#endif
>> +
>> +  // Reference wrappers should be handled:
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_front<f>(std::declval<int&>())),
>> +      decltype(bind_front<f>(std::ref(std::declval<int&>())))
>> +      >);
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_front<f>(std::declval<const int&>())),
>> +      decltype(bind_front<f>(std::cref(std::declval<int&>())))
>> +      >);
>> +  static_assert(!std::is_same_v<
>> +      decltype(bind_front<f>(std::ref(std::declval<int&>()))),
>> +      decltype(bind_front<f>(std::cref(std::declval<int&>())))
>> +      >);
>> +}
>> +
>> +void
>> +test02()
>> +{
>> +  struct quals
>> +  {
>> +    bool as_const;
>> +    bool as_lvalue;
>> +  };
>> +
>> +  struct F
>> +  {
>> +    quals operator()(int) & { return { false, true }; }
>> +    quals operator()(int) const & { return { true, true }; }
>> +    quals operator()(int) && { return { false, false }; }
>> +    quals operator()(int) const && { return { true, false }; }
>> +  };
>> +
>> +  {
>> +    constexpr F f;
>> +    auto g = bind_front<f>();
>> +    const auto& cg = g;
>> +    quals q;
>> +
>> +    // Constness and value category forwarded to the target object?
>> +#ifdef TRY
>> +    // [The following differ from the non-NTTP version of bind_front.]
>> +    q = g(0);
>> +    VERIFY( ! q.as_const && q.as_lvalue );
>> +    q = std::move(g)(0);
>> +    VERIFY( ! q.as_const && ! q.as_lvalue );
>> +    q = cg(0);
>> +    VERIFY( q.as_const && q.as_lvalue );
>> +    q = std::move(cg)(0);
>> +    VERIFY( q.as_const && ! q.as_lvalue );
>> +#endif
>> +  }
>> +  {
>> +    constexpr F f;
>> +    auto g = bind_front<f>(0);
>> +    const auto& cg = g;
>> +    quals q;
>> +
>> +    // Constness and value category forwarded to the target object?
>> +#ifdef TRY
>> +    // [The following differ from the non-NTTP version of bind_front.]
>> +    q = g();
>> +    VERIFY( ! q.as_const && q.as_lvalue );
>> +    q = std::move(g)();
>> +    VERIFY( ! q.as_const && ! q.as_lvalue );
>> +    q = cg();
>> +    VERIFY( q.as_const && q.as_lvalue );
>> +    q = std::move(cg)();
>> +    VERIFY( q.as_const && ! q.as_lvalue );
>> +#endif
>> +  }
>> +}
>> +
>> +void
>> +test03()
>> +{
>> +  struct F
>> +  {
>> +    int& operator()(int& i, void*) { return i; }
>> +    void* operator()(int, void* p) const { return p; }
>> +  };
>> +
>> +  int i = 5;
>> +  void* vp = &vp; // arbitrary void* value
>> +
>> +  constexpr F f;
>> +  auto g1 = bind_front<f>(i); // call wrapper has bound arg of type int
>> +  using G1 = decltype(g1);
>> +  // Invoking G1& will pass g1's bound arg as int&, so calls first
>> overload:
>> +#ifdef TRY_CT
>> +  // [The following differs from the non-NTTP version of bind_front.]
>> +  static_assert(is_invocable_r_v<int&, G1&, void*>);
>> +#endif
>> +
>> +  // Invoking const G1& or G&& calls second overload:
>> +  static_assert(is_invocable_r_v<void*, const G1&, void*>);
>> +  static_assert(is_invocable_r_v<void*, G1&&, void*>);
>> +  void* p1 = static_cast<G1&&>(g1)(vp);
>> +  VERIFY( p1 == vp );
>> +
>> +  // And can call first overload on const G6:
>> +  auto g2 = bind_front<f>(std::ref(i)); // bound arg of type int&
>> +  using G2 = decltype(g2);
>> +  // Bound arg always forwarded as int& even from G2&& or const G2&
>> +#ifdef TRY_CT
>> +  // [The following differ from the non-NTTP version of bind_front.]
>> +  static_assert(is_invocable_r_v<int&, G2&, void*>);
>> +  static_assert(is_invocable_r_v<int&, G2&&, void*>);
>> +  // But cannot call first overload on const G2:
>> +  static_assert(is_invocable_r_v<void*, const G2&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G2&&, void*>);
>> +  int& i2 = g2(vp);
>> +  VERIFY( &i2 == &i );
>> +  int& i2r = static_cast<G2&&>(g2)(vp);
>> +  VERIFY( &i2r == &i );
>> +  void* p2 = const_cast<const G2&>(g2)(vp);
>> +  VERIFY( p2 == vp );
>> +#endif
>> +
>> +  auto g3 = bind_front<f>(std::cref(i)); // bound arg of type const int&
>> +  using G3 = decltype(g3);
>> +  // Bound arg always forwarded as const int& so can only call second
>> overload:
>> +  static_assert(is_invocable_r_v<void*, G3&, void*>);
>> +  static_assert(is_invocable_r_v<void*, G3&&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G3&, void*>);
>> +  static_assert(is_invocable_r_v<void*, const G3&&, void*>);
>> +
>> +#ifdef TRY_CT
>> +  // [The following differ from the non-NTTP version of bind_front.]
>> +  auto g4 = bind_front<g2>(nullptr);
>> +  using G4 = decltype(g4);
>> +  static_assert(is_invocable_r_v<int&, G4&>);
>> +  static_assert(is_invocable_r_v<int&, G4&&>);
>> +  static_assert(is_invocable_r_v<void*, const G4&>);
>> +  static_assert(is_invocable_r_v<void*, const G4&&>);
>> +#endif
>> +}
>> +
>> +constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
>> +
>> +consteval int
>> +test04()
>> +{
>> +  constexpr auto g = bind_front<f>();
>> +  VERIFY( std::is_empty_v<decltype(g)> );
>> +  if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
>> +  constexpr auto g1 = bind_front<f>(1);
>> +  if (!(g1(2, 3) == 1 + 2*2 + 3*3 )) return 6;
>> +  if (!(bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 )) return 5;
>> +  constexpr auto g2 = bind_front<f>(1, 2);
>> +  if (!(g2(3) == 1 + 2*2 + 3*3 )) return 4;
>> +  if (!(bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 )) return 3;
>> +  constexpr auto g3 = bind_front<f>(1, 2, 3);
>> +  if (!(g3() == 1 + 2*2 + 3*3)) return 2;
>> +  if (!(bind_front<g2>(3)() == 1 + 2*2 + 3*3 )) return 1;
>> +  return 0;
>> +}
>> +
>> +template <auto nnfp, auto nfp>
>> +constexpr void test05() {
>> +  VERIFY(bind_front<nnfp>(1)(2, 3) == 14);
>> +#ifdef TRY_CT  // Fails to sandbox the static_assert(fp) in
>> bind_front<fp>():
>> +  VERIFY(!requires { bind_front<nfp>(1); });
>> +#endif
>> +}
>> +
>> +int
>> +main()
>> +{
>> +  test01();
>> +  test02();
>> +  test03();
>> +  static_assert(test04() == 0);
>> +  constexpr int (*nnfp)(int, int, int) = f;
>> +  constexpr int (*nfp)(int, int, int) = nullptr;
>> +  test05<nnfp, nfp>();
>> +}
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
>> b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
>> new file mode 100644
>> index 00000000000..b9a74bd16c6
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
>> @@ -0,0 +1,113 @@
>> +// Test NTTP version of not_fn, from P2714
>> +
>> +// { dg-do run { target c++26 } }
>> +
>> +// #define TRY
>> +// #define TRY_CT  // turn on failing static asserts
>> +
>> +#ifndef __cpp_lib_bind_back
>> +# error "Feature test macro for bind_back is missing in <functional>"
>> +#elif __cpp_lib_bind_back < 202306L
>> +# error "Feature test macro for bind_back has wrong value in
>> <functional>"
>> +#endif
>> +
>> +#include <functional>
>> +#include <testsuite_hooks.h>
>> +
>> +using std::not_fn;
>> +
>> +int func(int, char) { return 0; }
>> +
>> +struct F
>> +{
>> +  bool operator()() { return false; }
>> +  bool operator()() const { return true; }
>> +  bool operator()(int) const { return false; }
>> +};
>> +
>> +void
>> +test01()
>> +{
>> +  auto f1 = not_fn<func>();
>> +  VERIFY( std::is_empty_v<decltype(f1)> );
>> +  VERIFY( f1(1, '2') == true );
>> +
>> +  auto f2 = not_fn<[] { return true; }>();
>> +  VERIFY( std::is_empty_v<decltype(f2)> );
>> +  VERIFY( f2() == false );
>> +
>> +  auto f3 = not_fn<F{}>();
>> +  VERIFY( f3() == false );  // Prefer the const member.
>> +  VERIFY( f3(1) == true );
>> +  const auto f4 = f3;
>> +  VERIFY( f4() == false );
>> +}
>> +
>> +void
>> +test04()
>> +{
>> +  struct abstract { virtual void f() = 0; };
>> +  struct derived : abstract { void f() { } };
>> +  struct F { bool operator()(abstract&) const { return false; } };
>> +  constexpr F f;
>> +  derived d;
>> +  VERIFY( not_fn<f>()(d) );
>> +}
>> +
>> +void
>> +test05()
>> +{
>> +  auto nf = std::not_fn<[] { return false; }>();
>> +  auto copy(nf); // PR libstdc++/70564
>> +}
>> +
>> +void
>> +test06()
>> +{
>> +  struct Boolean {
>> +    Boolean operator!() noexcept(false) { return *this; }
>> +  };
>> +  struct F {
>> +    Boolean operator()() { return {}; }
>> +  };
>> +  F f;
>> +  auto notf = std::not_fn<f>();
>> +  using NotF = decltype(notf);
>> +
>> +  // [This fails for NTTP not_fn. ]
>> +#ifdef TRY_CT
>> +  static_assert( std::is_invocable<NotF>::value, "cannot negate" );
>> +#endif
>> +
>> +  // [This check fails for NTTP not_fn, even though actually invoking it
>> fails. ]
>> +#ifdef TRY_CT
>> +  static_assert( !noexcept(notf()), "conversion to bool affects
>> noexcept" );
>> +#endif
>> +}
>> +
>> +void
>> +test07()
>> +{
>> +  struct NonNegatable { };  // there is no operator!(NonNegatable)
>> +  struct F {
>> +    NonNegatable operator()() const { return {}; }
>> +  };
>> +  F f;
>> +  constexpr auto notf = std::not_fn<f>();
>> +  using NotF = decltype(notf);
>> +
>> +#ifdef TRY_CT
>> +  // [This check fails for NTTP not_fn, even though actually invoking it
>> fails. ]
>> +  static_assert( !std::is_invocable<NotF>::value, "cannot negate" );
>> +#endif
>> +}
>> +
>> +int
>> +main()
>> +{
>> +  test01();
>> +  test04();
>> +  test05();
>> +  test06();
>> +  test07();
>> +}
>> diff --git
>> a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
>> b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
>> index e3e92076f5c..5e835d684fd 100644
>> --- a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
>> +++ b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
>> @@ -57,6 +57,13 @@ namespace std {
>>    template <class Predicate>
>>    _GLIBCXX14_CONSTEXPR
>>    binary_negate<Predicate> not2(const Predicate&);
>> +#ifdef __cpp_lib_not_fn
>> +  template <typename F> _GLIBCXX20_CONSTEXPR auto not_fn(F&&)
>> +  noexcept(std::is_nothrow_constructible<std::decay_t<F>, F&&>::value);
>> +#if __cpp_lib_not_fn >= 2020306
>> +  template <auto f> constexpr auto not_fn() noexcept;
>> +#endif
>> +#endif
>>
>>    //  lib.binders, binders:
>>    template <class Operation>  class binder1st;
>> @@ -65,6 +72,20 @@ namespace std {
>>    template <class Operation> class binder2nd;
>>    template <class Operation, class T>
>>    binder2nd<Operation> bind2nd(const Operation&, const T&);
>> +#ifdef __cpp_lib_bind_front
>> +  template <typename F, typename... Args>
>> +    _GLIBCXX20_CONSTEXPR auto bind_front(F&&, Args&&...);
>> +#if __cpp_lib_bind_front >= 202306
>> +  template <auto f, typename... Args> constexpr auto
>> bind_front(Args&&...);
>> +#endif
>> +#endif
>> +#ifdef __cpp_lib_bind_back
>> +  template <typename F, typename... Args>
>> +    _GLIBCXX20_CONSTEXPR auto bind_back(F&&, Args&&...);
>> +#if __cpp_lib_bind_back >= 202306
>> +  template <auto f, typename... Args> constexpr auto
>> bind_back(Args&&...);
>> +#endif
>> +#endif
>>
>>    //  lib.function.pointer.adaptors, adaptors:
>>    template <class Arg, class Result> class pointer_to_unary_function;
>> --
>> 2.50.0
>>
>>

Reply via email to