[PATCHv2] libstdc++: Add NTTP bind_front, -back, not_fn (P2714) [PR119744]
Nathan Myers
ncm@cantrip.org
Fri Jul 18 10:32:19 GMT 2025
* I rewrote the implementation to more closely resemble the
non-NTTP version, and fix the various noexcept and requires
omissions.
* 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.
* 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.
* 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
+ 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
+ operator()(_Args&&... __args)
+ noexcept(
+ is_nothrow_invocable_v<_Fn, _Args...> and
+ noexcept(_S_not<invoke_result_t<_Fn, _Args...>>(
+ declval<invoke_result_t<_Fn, _Args...>>()) ))
+ requires (
+ is_invocable_v<_Fn, _Args...> and
+ is_invocable_v<
+ decltype(_S_not<invoke_result_t<_Fn, _Args...>>),
+ invoke_result_t<_Fn, _Args...> >)
+ {
+ return ! std::invoke(__fn, std::forward<_Args>(__args)...);
+ }
+ };
+
+ /** Wrap a function type to create a function object that negates its result.
+ *
+ * The function template `std::not_fn` creates a "forwarding call wrapper",
+ * which is a function object that when called forwards its arguments to
+ * its invocable template argument.
+ *
+ * The result of invoking the wrapper is the negation (using `!`) of
+ * the wrapped function object.
+ *
+ * @ingroup functors
+ * @since C++26
+ */
+ template<auto __fn>
+ constexpr auto
+ not_fn() noexcept
+ {
+ using _Fn = decltype(__fn);
+ if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) {
+ static_assert(__fn != nullptr);
+ }
+ return _Not_fn_nttp<__fn>{};
+ }
+#endif // __cpp_lib_not_fn >= 202306
+#endif // __cpp_lib_not_fn
#if __cplusplus >= 201703L
// Searchers
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
index c31d3228815..feedead477b 100644
--- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
@@ -149,23 +149,23 @@ test03()
static_assert(is_invocable_r_v<void*, const G4&&>);
}
-constexpr int f(int i, int j, int k) { return i + 2*(j + k); }
+constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
-constexpr bool
+constexpr int
test04()
{
auto g = bind_back(f);
- VERIFY( g(1, 2, 3) == 1 + 2*(2 + 3) );
+ if (!( g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
auto g1 = bind_back(f, 1);
- VERIFY( g1(2, 3) == 2 + 2*(3 + 1) );
- VERIFY( bind_back(g, 1)(2, 3) == 2 + 2*(3 + 1) );
+ if (!( g1(2, 3) == 3*1 + 2 + 3*2)) return 6;
+ if (!( bind_back(g, 1)(2, 3) == 3*1 + 2 + 2*3 )) return 5;
auto g2 = bind_back(f, 1, 2);
- VERIFY( g2(3) == 3 + 2*(1 + 2) );
- VERIFY( bind_back(g1, 2)(3) == 3 + 2*(2 + 1) );
+ if (!( g2(3) == 3 + 2*1 + 3*2)) return 4;
+ if (!( bind_back(g1, 2)(3) == 3*1 + 2*2 + 3 )) return 3;
auto g3 = bind_back(f, 1, 2, 3);
- VERIFY( g3() == 1 + 2*(2 + 3) );
- VERIFY( bind_back(g2, 3)() == 3 + 2*(1 + 2) );
- return true;
+ if (!( g3() == 1 + 2*2 + 3*3 )) return 2;
+ if (!( bind_back(g2, 3)() == 3*1 + 1*2 + 2*3)) return 1;
+ return 0;
}
int
@@ -174,5 +174,5 @@ main()
test01();
test02();
test03();
- static_assert(test04());
+ static_assert(test04() == 0);
}
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
new file mode 100644
index 00000000000..467502c7473
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
@@ -0,0 +1,191 @@
+// { dg-do run { target c++26 } }
+// { dg-add-options no_pch }
+
+// Test NTTP bind_back<f>(Args...), P2714
+
+#include <functional>
+
+#ifndef __cpp_lib_bind_back
+# error "Feature test macro for bind_back is missing in <functional>"
+#elif __cpp_lib_bind_back < 202306L
+# error "Feature test macro for bind_back has wrong value in <functional>"
+#endif
+
+#include <testsuite_hooks.h>
+
+using std::bind_back;
+using std::is_same_v;
+using std::is_invocable_v;
+using std::is_invocable_r_v;
+
+void
+test01()
+{
+ struct F { void operator()() {} };
+ constexpr F f{};
+
+ // [The following differ from the non-NTTP version of bind_back.]
+ // Arguments should be decayed:
+ // static_assert(std::is_same_v<
+ // decltype(bind_back<f>(std::declval<int>())),
+ // decltype(bind_back<f>(std::declval<int&>()))
+ // >);
+ // static_assert(std::is_same_v<
+ // decltype(bind_back<f>(std::declval<int>())),
+ // decltype(bind_back<f>(std::declval<const int&>()))
+ // >);
+
+ // Reference wrappers should be handled:
+ static_assert(!std::is_same_v<
+ decltype(bind_back<f>(std::declval<int&>())),
+ decltype(bind_back<f>(std::ref(std::declval<int&>())))
+ >);
+ static_assert(!std::is_same_v<
+ decltype(bind_back<f>(std::declval<const int&>())),
+ decltype(bind_back<f>(std::cref(std::declval<int&>())))
+ >);
+ static_assert(!std::is_same_v<
+ decltype(bind_back<f>(std::ref(std::declval<int&>()))),
+ decltype(bind_back<f>(std::cref(std::declval<int&>())))
+ >);
+}
+
+void
+test02()
+{
+ struct quals
+ {
+ bool as_const;
+ bool as_lvalue;
+ };
+
+ struct F
+ {
+ quals operator()() & { return { false, true }; }
+ quals operator()() const & { return { true, true }; }
+ quals operator()() && { return { false, false }; }
+ quals operator()() const && { return { true, false }; }
+ };
+
+ constexpr F f;
+ auto g = bind_back<f>();
+ const auto& cg = g;
+ quals q;
+
+ // Constness and value category forwarded to the target object?
+ q = g();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && ! q.as_lvalue );
+}
+
+void
+test03()
+{
+ struct F
+ {
+ int& operator()(void*, int& i) { return i; }
+ void* operator()(void* p, int) const { return p; }
+ };
+
+ int i = 5;
+ void* vp = &vp; // arbitrary void* value
+
+ constexpr F f;
+ auto g1 = bind_back<f>(i); // call wrapper has bound arg of type int
+ using G1 = decltype(g1);
+ // Invoking G1& will pass g1's bound arg as int&, so calls first overload:
+ // [The following differs from the non-NTTP version of bind_back.]
+ static_assert(!is_invocable_r_v<int&, G1&, void*>);
+
+ // Invoking const G1& or G&& calls second overload:
+ static_assert(is_invocable_r_v<void*, const G1&, void*>);
+ static_assert(is_invocable_r_v<void*, G1&&, void*>);
+ void* p1 = static_cast<G1&&>(g1)(vp);
+ VERIFY( p1 == vp );
+
+ // And can call first overload on const G6:
+ auto g2 = bind_back<f>(std::ref(i)); // bound arg of type int&
+ using G2 = decltype(g2);
+ // Bound arg always forwarded as int& even from G2&& or const G2&
+ // [The following differs from the non-NTTP version of bind_back.]
+ // static_assert(is_invocable_r_v<int&, G2&, void*>);
+ // static_assert(is_invocable_r_v<int&, G2&&, void*>);
+
+ // But cannot call first overload on const G2:
+ // [The following differs from the non-NTTP version of bind_back.]
+ // static_assert(is_invocable_r_v<void*, const G2&, void*>);
+ // static_assert(is_invocable_r_v<void*, const G2&&, void*>);
+ // [The following differ from the non-NTTP version of bind_back.]
+ //void* i2 = g2(vp);
+ //VERIFY( &i2 == &i );
+ //void* i2r = static_cast<G2&&>(g2)(vp);
+ //VERIFY( iv2r == &i );
+
+ void* p2 = const_cast<const G2&>(g2)(vp);
+ VERIFY( p2 == vp );
+
+ auto g3 = bind_back<f>(std::cref(i)); // bound arg of type const int&
+ using G3 = decltype(g3);
+ // Bound arg always forwarded as const int& so can only call second overload:
+ static_assert(is_invocable_r_v<void*, G3&, void*>);
+ static_assert(is_invocable_r_v<void*, G3&&, void*>);
+ static_assert(is_invocable_r_v<void*, const G3&, void*>);
+ static_assert(is_invocable_r_v<void*, const G3&&, void*>);
+
+ // [The following differ from the non-NTTP version of bind_back.]
+ // auto g4 = bind_back<g2>(nullptr);
+ // using G4 = decltype(g4);
+ // static_assert(is_invocable_r_v<int&, G4&>);
+ // static_assert(is_invocable_r_v<int&, G4&&>);
+ // static_assert(is_invocable_r_v<void*, const G4&>);
+ // static_assert(is_invocable_r_v<void*, const G4&&>);
+}
+
+constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
+
+consteval int
+test04()
+{
+ constexpr auto g = bind_back<f>();
+ if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
+ constexpr auto g1 = bind_back<f>(1);
+ if (!(g1(2, 3) == 3*1 + 1*2 + 2*3 )) return 6;
+ if (!(bind_back<g>(1)(2, 3) == 3*1 + 1*2 + 2*3 )) return 5;
+ constexpr auto g2 = bind_back<f>(1, 2);
+ if (!(g2(3) == 2*1 + 3*2 + 1*3 )) return 4;
+
+ // [The following differs from the non-NTTP version of bind_back.]
+ // if (!(bind_back<g1>(2)(3) == 3*1 + 2*2 + 1*3 )) return 3;
+
+ constexpr auto g3 = bind_back<f>(1, 2, 3);
+ if (!(g3() == 1 + 2*2 + 3*3)) return 2;
+
+ // [The following differs from the non-NTTP version of bind_back.]
+ // if (!(bind_back<g2>(3)() == 1*2 + 2*3 + 3*1 )) return 1;
+ return 0;
+}
+
+template <auto nnfp, auto nfp>
+void test05() {
+ VERIFY(bind_back<nnfp>(1)(2, 3) == 3*1 + 1*2 + 2*3);
+#if 0 // Fails to sandbox the static_assert(fp) in bind_back<fp>():
+ VERIFY(!requires { bind_back<nfp>(1); });
+#endif
+}
+
+int
+main()
+{
+ test01();
+ test02();
+ test03();
+ static_assert(test04() == 0);
+ constexpr int (*nnfp)(int, int, int) = f;
+ constexpr int (*nfp)(int, int, int) = nullptr;
+ test05<nnfp, nfp>();
+}
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
index 57482c52263..b038889fbb4 100644
--- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc
@@ -149,22 +149,22 @@ test03()
static_assert(is_invocable_r_v<void*, const G4&&>);
}
-int f(int i, int j, int k) { return i + j + k; }
+int f(int i, int j, int k) { return i + 2*j + 3*k; }
void
test04()
{
auto g = bind_front(f);
- VERIFY( g(1, 2, 3) == 6 );
+ VERIFY( g(1, 2, 3) == 14 );
auto g1 = bind_front(f, 1);
- VERIFY( g1(2, 3) == 6 );
- VERIFY( bind_front(g, 1)(2, 3) == 6 );
+ VERIFY( g1(2, 3) == 14 );
+ VERIFY( bind_front(g, 1)(2, 3) == 14 );
auto g2 = bind_front(f, 1, 2);
- VERIFY( g2(3) == 6 );
- VERIFY( bind_front(g1, 2)(3) == 6 );
+ VERIFY( g2(3) == 14 );
+ VERIFY( bind_front(g1, 2)(3) == 14 );
auto g3 = bind_front(f, 1, 2, 3);
- VERIFY( g3() == 6 );
- VERIFY( bind_front(g2, 3)() == 6 );
+ VERIFY( g3() == 14 );
+ VERIFY( bind_front(g2, 3)() == 14 );
}
int
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
new file mode 100644
index 00000000000..a16adfbd379
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
@@ -0,0 +1,188 @@
+// { dg-do run { target c++26 } }
+// { dg-add-options no_pch }
+
+// Test NTTP bind_front<f>(Args...), P2714
+
+#include <functional>
+
+#ifndef __cpp_lib_bind_front
+# error "Feature test macro for bind_front is missing in <functional>"
+#elif __cpp_lib_bind_front < 201902L
+# error "Feature test macro for bind_front has wrong value in <functional>"
+#endif
+
+#include <testsuite_hooks.h>
+
+using std::bind_front;
+using std::is_same_v;
+using std::is_invocable_v;
+using std::is_invocable_r_v;
+
+void
+test01()
+{
+ struct F { void operator()() {} };
+ constexpr F f{};
+
+ // [The following differ from the non-NTTP version of bind_front.]
+ // Arguments should be decayed:
+ // static_assert(std::is_same_v<
+ // decltype(bind_front<f>(std::declval<int>())),
+ // decltype(bind_front<f>(std::declval<int&>()))
+ // >);
+ // static_assert(std::is_same_v<
+ // decltype(bind_front<f>(std::declval<int>())),
+ // decltype(bind_front<f>(std::declval<const int&>()))
+ // >);
+
+ // Reference wrappers should be handled:
+ static_assert(!std::is_same_v<
+ decltype(bind_front<f>(std::declval<int&>())),
+ decltype(bind_front<f>(std::ref(std::declval<int&>())))
+ >);
+ static_assert(!std::is_same_v<
+ decltype(bind_front<f>(std::declval<const int&>())),
+ decltype(bind_front<f>(std::cref(std::declval<int&>())))
+ >);
+ static_assert(!std::is_same_v<
+ decltype(bind_front<f>(std::ref(std::declval<int&>()))),
+ decltype(bind_front<f>(std::cref(std::declval<int&>())))
+ >);
+}
+
+void
+test02()
+{
+ struct quals
+ {
+ bool as_const;
+ bool as_lvalue;
+ };
+
+ struct F
+ {
+ quals operator()() & { return { false, true }; }
+ quals operator()() const & { return { true, true }; }
+ quals operator()() && { return { false, false }; }
+ quals operator()() const && { return { true, false }; }
+ };
+
+ constexpr F f;
+ auto g = bind_front<f>();
+ const auto& cg = g;
+ quals q;
+
+ // Constness and value category forwarded to the target object?
+ q = g();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && ! q.as_lvalue );
+}
+
+void
+test03()
+{
+ struct F
+ {
+ int& operator()(int& i, void*) { return i; }
+ void* operator()(int, void* p) const { return p; }
+ };
+
+ int i = 5;
+ void* vp = &vp; // arbitrary void* value
+
+ constexpr F f;
+ auto g1 = bind_front<f>(i); // call wrapper has bound arg of type int
+ using G1 = decltype(g1);
+ // Invoking G1& will pass g1's bound arg as int&, so calls first overload:
+ // [The following differs from the non-NTTP version of bind_front.]
+ // static_assert(is_invocable_r_v<int&, G1&, void*>);
+
+ // Invoking const G1& or G&& calls second overload:
+ static_assert(is_invocable_r_v<void*, const G1&, void*>);
+ static_assert(is_invocable_r_v<void*, G1&&, void*>);
+ void* p1 = static_cast<G1&&>(g1)(vp);
+ VERIFY( p1 == vp );
+
+ // And can call first overload on const G6:
+ auto g2 = bind_front<f>(std::ref(i)); // bound arg of type int&
+ using G2 = decltype(g2);
+ // Bound arg always forwarded as int& even from G2&& or const G2&
+ // static_assert(is_invocable_r_v<int&, G2&, void*>);
+ // static_assert(is_invocable_r_v<int&, G2&&, void*>);
+
+ // [The following differ from the non-NTTP version of bind_front.]
+ // But cannot call first overload on const G2:
+ // static_assert(is_invocable_r_v<void*, const G2&, void*>);
+ // static_assert(is_invocable_r_v<void*, const G2&&, void*>);
+ // int& i2 = g2(vp);
+ // VERIFY( &i2 == &i );
+ // int& i2r = static_cast<G2&&>(g2)(vp);
+ // VERIFY( &i2r == &i );
+ // void* p2 = const_cast<const G2&>(g2)(vp);
+ // VERIFY( p2 == vp );
+
+ auto g3 = bind_front<f>(std::cref(i)); // bound arg of type const int&
+ using G3 = decltype(g3);
+ // Bound arg always forwarded as const int& so can only call second overload:
+ static_assert(is_invocable_r_v<void*, G3&, void*>);
+ static_assert(is_invocable_r_v<void*, G3&&, void*>);
+ static_assert(is_invocable_r_v<void*, const G3&, void*>);
+ static_assert(is_invocable_r_v<void*, const G3&&, void*>);
+
+ // [The following differ from the non-NTTP version of bind_front.]
+ // auto g4 = bind_front<g2>(nullptr);
+ // using G4 = decltype(g4);
+ // static_assert(is_invocable_r_v<int&, G4&>);
+ // static_assert(is_invocable_r_v<int&, G4&&>);
+ // static_assert(is_invocable_r_v<void*, const G4&>);
+ // static_assert(is_invocable_r_v<void*, const G4&&>);
+}
+
+constexpr int f(int i, int j, int k) { return i + 2*j + 3*k; }
+
+consteval int
+test04()
+{
+ constexpr auto g = bind_front<f>();
+ if (!(g(1, 2, 3) == 1 + 2*2 + 3*3 )) return 7;
+ constexpr auto g1 = bind_front<f>(1);
+ if (!(g1(2, 3) == 1 + 2*2 + 3*3 )) return 6;
+ if (!(bind_front<g>(1)(2, 3) == 1 + 2*2 + 3*3 )) return 5;
+ constexpr auto g2 = bind_front<f>(1, 2);
+ if (!(g2(3) == 1 + 2*2 + 3*3 )) return 4;
+
+ // [The following differ from the non-NTTP version of bind_front.]
+ // if (!(bind_front<g1>(2)(3) == 1 + 2*2 + 3*3 )) return 3;
+
+ constexpr auto g3 = bind_front<f>(1, 2, 3);
+ if (!(g3() == 1 + 2*2 + 3*3)) return 2;
+
+ // [The following differ from the non-NTTP version of bind_front.]
+ // if (!(bind_front<g2>(3)() == 1 + 2*2 + 3*3 )) return 1;
+ return 0;
+}
+
+template <auto nnfp, auto nfp>
+constexpr void test05() {
+ VERIFY(bind_front<nnfp>(1)(2, 3) == 14);
+#if 0 // Fails to sandbox the static_assert(fp) in bind_front<fp>():
+ VERIFY(!requires { bind_front<nfp>(1); });
+#endif
+}
+
+int
+main()
+{
+ test01();
+ test02();
+ test03();
+ static_assert(test04() == 0);
+ constexpr int (*nnfp)(int, int, int) = f;
+ constexpr int (*nfp)(int, int, int) = nullptr;
+ test05<nnfp, nfp>();
+}
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
new file mode 100644
index 00000000000..b7c06c103a7
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
@@ -0,0 +1,101 @@
+// Test NTTP version of not_fn, from P2714
+
+// { dg-do run { target c++26 } }
+
+#ifndef __cpp_lib_bind_back
+# error "Feature test macro for bind_back is missing in <functional>"
+#elif __cpp_lib_bind_back < 202306L
+# error "Feature test macro for bind_back has wrong value in <functional>"
+#endif
+
+#include <functional>
+#include <testsuite_hooks.h>
+
+using std::not_fn;
+
+int func(int, char) { return 0; }
+
+struct F
+{
+ bool operator()() { return false; }
+ bool operator()() const { return true; }
+ bool operator()(int) const { return false; }
+};
+
+void
+test01()
+{
+ auto f1 = not_fn<func>();
+ VERIFY( f1(1, '2') == true );
+
+ auto f2 = not_fn<[] { return true; }>();
+ VERIFY( f2() == false );
+
+ auto f3 = not_fn<F{}>();
+ VERIFY( f3() == false ); // Prefer the const member.
+ VERIFY( f3(1) == true );
+ const auto f4 = f3;
+ VERIFY( f4() == false );
+}
+
+void
+test04()
+{
+ struct abstract { virtual void f() = 0; };
+ struct derived : abstract { void f() { } };
+ struct F { bool operator()(abstract&) const { return false; } };
+ constexpr F f;
+ derived d;
+ VERIFY( not_fn<f>()(d) );
+}
+
+void
+test05()
+{
+ auto nf = std::not_fn<[] { return false; }>();
+ auto copy(nf); // PR libstdc++/70564
+}
+
+void
+test06()
+{
+ struct Boolean {
+ Boolean operator!() noexcept(false) { return *this; }
+ };
+ struct F {
+ Boolean operator()() { return {}; }
+ };
+ F f;
+ auto notf = std::not_fn<f>();
+ using NotF = decltype(notf);
+
+ // [This check fails for NTTP not_fn, even though actually invoking it fails. ]
+ // static_assert( std::is_invocable<NotF>::value, "cannot negate" );
+ //
+ static_assert( !noexcept(notf()), "conversion to bool affects noexcept" );
+}
+
+void
+test07()
+{
+ struct NonNegatable { }; // there is no operator!(NonNegatable)
+ struct F {
+ NonNegatable operator()() const { return {}; }
+ };
+ F f;
+ constexpr auto notf = std::not_fn<f>();
+ using NotF = decltype(notf);
+
+ // [This check fails for NTTP not_fn, even though actually invoking it fails. ]
+ // static_assert( !std::is_invocable<NotF>::value, "cannot negate" );
+}
+
+int
+main()
+{
+ test01();
+ test04();
+ test05();
+ test06();
+ test07();
+}
diff --git a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
index e3e92076f5c..5e835d684fd 100644
--- a/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
+++ b/libstdc++-v3/testsuite/20_util/headers/functional/synopsis.cc
@@ -57,6 +57,13 @@ namespace std {
template <class Predicate>
_GLIBCXX14_CONSTEXPR
binary_negate<Predicate> not2(const Predicate&);
+#ifdef __cpp_lib_not_fn
+ template <typename F> _GLIBCXX20_CONSTEXPR auto not_fn(F&&)
+ noexcept(std::is_nothrow_constructible<std::decay_t<F>, F&&>::value);
+#if __cpp_lib_not_fn >= 2020306
+ template <auto f> constexpr auto not_fn() noexcept;
+#endif
+#endif
// lib.binders, binders:
template <class Operation> class binder1st;
@@ -65,6 +72,20 @@ namespace std {
template <class Operation> class binder2nd;
template <class Operation, class T>
binder2nd<Operation> bind2nd(const Operation&, const T&);
+#ifdef __cpp_lib_bind_front
+ template <typename F, typename... Args>
+ _GLIBCXX20_CONSTEXPR auto bind_front(F&&, Args&&...);
+#if __cpp_lib_bind_front >= 202306
+ template <auto f, typename... Args> constexpr auto bind_front(Args&&...);
+#endif
+#endif
+#ifdef __cpp_lib_bind_back
+ template <typename F, typename... Args>
+ _GLIBCXX20_CONSTEXPR auto bind_back(F&&, Args&&...);
+#if __cpp_lib_bind_back >= 202306
+ template <auto f, typename... Args> constexpr auto bind_back(Args&&...);
+#endif
+#endif
// lib.function.pointer.adaptors, adaptors:
template <class Arg, class Result> class pointer_to_unary_function;
--
2.50.0
More information about the Libstdc++
mailing list