-
Notifications
You must be signed in to change notification settings - Fork 12k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc++][functional] Implement not_fn<NTTP>
#86133
base: main
Are you sure you want to change the base?
[libc++][functional] Implement not_fn<NTTP>
#86133
Conversation
@llvm/pr-subscribers-libcxx Author: Jakub Mazurkiewicz (JMazurkiewicz) ChangesImplement Current implementation does not use Patch is 22.79 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/86133.diff 13 Files Affected:
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index b213f430aa5922..bdf9e57890f90c 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -428,6 +428,8 @@ Status
--------------------------------------------------- -----------------
``__cpp_lib_linalg`` *unimplemented*
--------------------------------------------------- -----------------
+ ``__cpp_lib_not_fn`` ``202306L``
+ --------------------------------------------------- -----------------
``__cpp_lib_out_ptr`` *unimplemented*
--------------------------------------------------- -----------------
``__cpp_lib_ratio`` ``202306L``
diff --git a/libcxx/docs/Status/Cxx2c.rst b/libcxx/docs/Status/Cxx2c.rst
index a7ebc4662f517c..81eeee3367d8c2 100644
--- a/libcxx/docs/Status/Cxx2c.rst
+++ b/libcxx/docs/Status/Cxx2c.rst
@@ -40,6 +40,7 @@ Paper Status
.. note::
.. [#note-P2510R3] This paper is applied as DR against C++20. (MSVC STL and libstdc++ will do the same.)
+ .. [#note-P2714R1] ``not_fn`` is done.
.. _issues-status-cxx2c:
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 4a5443dea115c8..a085dc899852f9 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -24,7 +24,7 @@
"`P1383R2 <https://wg21.link/P1383R2>`__","LWG","More ``constexpr`` for ``<cmath>`` and ``<complex>``","Varna June 2023","","",""
"`P2734R0 <https://wg21.link/P2734R0>`__","LWG","Adding the new SI prefixes","Varna June 2023","|Complete|","17.0",""
"`P2548R6 <https://wg21.link/P2548R6>`__","LWG","``copyable_function``","Varna June 2023","","",""
-"`P2714R1 <https://wg21.link/P2714R1>`__","LWG","Bind front and back to NTTP callables","Varna June 2023","","",""
+"`P2714R1 <https://wg21.link/P2714R1>`__","LWG","Bind front and back to NTTP callables","Varna June 2023","|Partial| [#note-P2714R1]_","19.0",""
"`P2630R4 <https://wg21.link/P2630R4>`__","LWG","``submdspan``","Varna June 2023","","",""
"","","","","","",""
"`P0543R3 <https://wg21.link/P0543R3>`__","LWG","Saturation arithmetic","Kona November 2023","|Complete|","18.0",""
diff --git a/libcxx/include/__functional/not_fn.h b/libcxx/include/__functional/not_fn.h
index 4b3ce5524a7434..d5e6d5f9939d2d 100644
--- a/libcxx/include/__functional/not_fn.h
+++ b/libcxx/include/__functional/not_fn.h
@@ -16,6 +16,8 @@
#include <__type_traits/decay.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_constructible.h>
+#include <__type_traits/is_member_pointer.h>
+#include <__type_traits/is_pointer.h>
#include <__utility/forward.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -48,6 +50,27 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 auto not_fn(_Fn&& __f) {
#endif // _LIBCPP_STD_VER >= 17
+#if _LIBCPP_STD_VER >= 26
+
+template <auto _Fn>
+struct __nttp_not_fn_t {
+ template <class... _Args>
+ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const
+ noexcept(noexcept(!std::invoke(_Fn, std::forward<_Args>(__args)...)))
+ -> decltype(!std::invoke(_Fn, std::forward<_Args>(__args)...)) {
+ return !std::invoke(_Fn, std::forward<_Args>(__args)...);
+ }
+};
+
+template <auto _Fn>
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto not_fn() noexcept {
+ if constexpr (using _Ty = decltype(_Fn); is_pointer_v<_Ty> || is_member_pointer_v<_Ty>)
+ static_assert(_Fn != nullptr, "f cannot be equal to nullptr");
+ return __nttp_not_fn_t<_Fn>();
+}
+
+#endif // _LIBCPP_STD_VER >= 26
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___FUNCTIONAL_NOT_FN_H
diff --git a/libcxx/include/functional b/libcxx/include/functional
index a2774a48bda0ee..faf97f17daec3d 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -205,7 +205,9 @@ template <class Predicate> // deprecated in C++17, removed in C++20
binary_negate<Predicate> not2(const Predicate& pred);
template <class F>
-constexpr unspecified not_fn(F&& f); // C++17, constexpr in C++20
+ constexpr unspecified not_fn(F&& f); // C++17, constexpr in C++20
+template <auto f>
+ constexpr unspecified not_fn() noexcept; // C++26
template<class T> struct is_bind_expression;
template<class T> struct is_placeholder;
diff --git a/libcxx/include/version b/libcxx/include/version
index 3bd296e34aa4e3..8fda253f4051d3 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -154,7 +154,8 @@ __cpp_lib_nonmember_container_access 201411L <array> <deque>
<iterator> <list> <map>
<regex> <set> <string>
<unordered_map> <unordered_set> <vector>
-__cpp_lib_not_fn 201603L <functional>
+__cpp_lib_not_fn 202306L <functional>
+ 201603L // C++17
__cpp_lib_null_iterators 201304L <iterator>
__cpp_lib_optional 202110L <optional>
201606L // C++17
@@ -507,6 +508,8 @@ __cpp_lib_within_lifetime 202306L <type_traits>
// # define __cpp_lib_function_ref 202306L
// # define __cpp_lib_hazard_pointer 202306L
// # define __cpp_lib_linalg 202311L
+# undef __cpp_lib_not_fn
+# define __cpp_lib_not_fn 202306L
# undef __cpp_lib_out_ptr
// # define __cpp_lib_out_ptr 202311L
# define __cpp_lib_ratio 202306L
diff --git a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.compile.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.compile.pass.cpp
new file mode 100644
index 00000000000000..8390bd76f5723d
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.compile.pass.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <functional>
+
+// Test implementation-defined properties of std::not_fn<NTTP>.
+
+#include <functional>
+#include <type_traits>
+
+struct NotEmptyFunctionObject {
+ bool val = true;
+ bool operator()() const; // not defined
+};
+
+void test() {
+ using ResultWithEmptyFuncObject = decltype(std::not_fn<std::false_type{}>());
+ static_assert(std::is_empty_v<ResultWithEmptyFuncObject>);
+
+ using ResultWithNotEmptyFuncObject = decltype(std::not_fn<NotEmptyFunctionObject{}>());
+ static_assert(std::is_empty_v<ResultWithNotEmptyFuncObject>);
+}
diff --git a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp
new file mode 100644
index 00000000000000..50d956e73fc09d
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <functional>
+
+// Test the libc++ extension that std::not_fn<NTTP> is marked as [[nodiscard]].
+
+#include <functional>
+#include <type_traits>
+
+void test() {
+ using F = std::true_type;
+ std::not_fn<F{}>(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
index 72c96c62b64c45..a1252288159a1c 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp
@@ -28,6 +28,7 @@
__cpp_lib_invoke_r 202106L [C++23]
__cpp_lib_move_only_function 202110L [C++23]
__cpp_lib_not_fn 201603L [C++17]
+ 202306L [C++26]
__cpp_lib_ranges 202207L [C++20]
__cpp_lib_result_of_sfinae 201210L [C++14]
__cpp_lib_transparent_operators 201210L [C++14]
@@ -516,8 +517,8 @@
# ifndef __cpp_lib_not_fn
# error "__cpp_lib_not_fn should be defined in c++26"
# endif
-# if __cpp_lib_not_fn != 201603L
-# error "__cpp_lib_not_fn should have the value 201603L in c++26"
+# if __cpp_lib_not_fn != 202306L
+# error "__cpp_lib_not_fn should have the value 202306L in c++26"
# endif
# ifndef __cpp_lib_ranges
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 5501587915ffa0..72192a7673a038 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -144,6 +144,7 @@
__cpp_lib_node_extract 201606L [C++17]
__cpp_lib_nonmember_container_access 201411L [C++17]
__cpp_lib_not_fn 201603L [C++17]
+ 202306L [C++26]
__cpp_lib_null_iterators 201304L [C++14]
__cpp_lib_optional 201606L [C++17]
202110L [C++23]
@@ -6968,8 +6969,8 @@
# ifndef __cpp_lib_not_fn
# error "__cpp_lib_not_fn should be defined in c++26"
# endif
-# if __cpp_lib_not_fn != 201603L
-# error "__cpp_lib_not_fn should have the value 201603L in c++26"
+# if __cpp_lib_not_fn != 202306L
+# error "__cpp_lib_not_fn should have the value 202306L in c++26"
# endif
# ifndef __cpp_lib_null_iterators
diff --git a/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp
new file mode 100644
index 00000000000000..29df597704e1e8
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp
@@ -0,0 +1,294 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
+
+// <functional>
+
+// template<auto f> constexpr unspecified not_fn() noexcept;
+
+#include <functional>
+
+#include <bit>
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+class BooleanTestable {
+ bool val_;
+
+public:
+ constexpr explicit BooleanTestable(bool val) : val_(val) {}
+ constexpr operator bool() const { return val_; }
+ constexpr BooleanTestable operator!() const { return BooleanTestable{!val_}; }
+};
+
+LIBCPP_STATIC_ASSERT(std::__boolean_testable<BooleanTestable>);
+
+class FakeBool {
+ int val_;
+
+public:
+ constexpr FakeBool(int val) : val_(val) {}
+ constexpr FakeBool operator!() const { return FakeBool{-val_}; }
+ constexpr bool operator==(int other) const { return val_ == other; }
+};
+
+template <bool IsNoexcept>
+struct MaybeNoexceptFn {
+ bool operator()() const noexcept(IsNoexcept); // not defined
+};
+
+constexpr void basic_tests() {
+ { // Test constant functions
+ auto false_fn = std::not_fn<std::false_type{}>();
+ assert(false_fn());
+
+ auto true_fn = std::not_fn<std::true_type{}>();
+ assert(!true_fn());
+
+ static_assert(noexcept(std::not_fn<std::false_type{}>()));
+ static_assert(noexcept(std::not_fn<std::true_type{}>()));
+ }
+
+ { // Test function with one argument
+ auto is_odd = std::not_fn<[](auto x) { return x % 2 == 0; }>();
+ assert(is_odd(1));
+ assert(!is_odd(2));
+ assert(is_odd(3));
+ assert(!is_odd(4));
+ assert(is_odd(5));
+ }
+
+ { // Test function with multiple arguments
+ auto at_least_10 = [](auto... vals) { return (vals + ... + 0) >= 10; };
+ auto at_most_9 = std::not_fn<at_least_10>();
+ assert(at_most_9());
+ assert(at_most_9(1));
+ assert(at_most_9(1, 2, 3, 4, -1));
+ assert(at_most_9(3, 3, 2, 1, -2));
+ assert(!at_most_9(10, -1, 2));
+ assert(!at_most_9(5, 5));
+ static_assert(noexcept(std::not_fn<at_least_10>()));
+ }
+
+ { // Test function that returns boolean-testable type other than bool
+ auto is_product_even = [](auto... vals) { return BooleanTestable{(vals * ... * 1) % 2 == 0}; };
+ auto is_product_odd = std::not_fn<is_product_even>();
+ assert(is_product_odd());
+ assert(is_product_odd(1, 3, 5, 9));
+ assert(is_product_odd(3, 3, 3, 3));
+ assert(!is_product_odd(3, 5, 9, 11, 0));
+ assert(!is_product_odd(11, 7, 5, 3, 2));
+ static_assert(noexcept(std::not_fn<is_product_even>()));
+ }
+
+ { // Test function that returns non-boolean-testable type
+ auto sum = [](auto... vals) -> FakeBool { return (vals + ... + 0); };
+ auto negated_sum = std::not_fn<sum>();
+ assert(negated_sum() == 0);
+ assert(negated_sum(3) == -3);
+ assert(negated_sum(4, 5, 1, 3) == -13);
+ assert(negated_sum(4, 2, 5, 6, 1) == -18);
+ assert(negated_sum(-1, 3, 2, -8) == 4);
+ static_assert(noexcept(std::not_fn<sum>()));
+ }
+
+ { // Test member pointers
+ struct MemberPointerTester {
+ bool value = true;
+ constexpr bool not_value() const { return !value; }
+ constexpr bool value_and(bool other) noexcept { return value && other; }
+ };
+
+ MemberPointerTester tester;
+
+ auto not_mem_object = std::not_fn<&MemberPointerTester::value>();
+ assert(!not_mem_object(tester));
+ assert(!not_mem_object(std::as_const(tester)));
+ static_assert(noexcept(not_mem_object(tester)));
+ static_assert(noexcept(not_mem_object(std::as_const(tester))));
+
+ auto not_nullary_mem_fn = std::not_fn<&MemberPointerTester::not_value>();
+ assert(not_nullary_mem_fn(tester));
+ static_assert(!noexcept(not_nullary_mem_fn(tester)));
+
+ auto not_unary_mem_fn = std::not_fn<&MemberPointerTester::value_and>();
+ assert(not_unary_mem_fn(tester, false));
+ static_assert(noexcept(not_unary_mem_fn(tester, false)));
+ static_assert(!std::is_invocable_v<decltype(not_unary_mem_fn), const MemberPointerTester&, bool>);
+ }
+}
+
+constexpr void test_perfect_forwarding_call_wrapper() {
+ { // Make sure we call the correctly cv-ref qualified operator()
+ // based on the value category of the not_fn<NTTP> unspecified-type.
+ struct X {
+ constexpr FakeBool operator()() & { return 1; }
+ constexpr FakeBool operator()() const& { return 2; }
+ constexpr FakeBool operator()() && { return 3; }
+ constexpr FakeBool operator()() const&& { return 4; }
+ };
+
+ auto f = std::not_fn<X{}>();
+ using F = decltype(f);
+ assert(static_cast<F&>(f)() == -2);
+ assert(static_cast<const F&>(f)() == -2);
+ assert(static_cast<F&&>(f)() == -2);
+ assert(static_cast<const F&&>(f)() == -2);
+ }
+
+ // Call to `not_fn<NTTP>` unspecified-type's operator() should always result in call to const& overload of .
+ {
+ { // Make sure unspecified-type is still callable when we delete & overload.
+ struct X {
+ FakeBool operator()() & = delete;
+ FakeBool operator()() const&;
+ FakeBool operator()() &&;
+ FakeBool operator()() const&&;
+ };
+
+ using F = decltype(std::not_fn<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is not callable when we delete const& overload.
+ struct X {
+ FakeBool operator()() &;
+ FakeBool operator()() const& = delete;
+ FakeBool operator()() &&;
+ FakeBool operator()() const&&;
+ };
+
+ using F = decltype(std::not_fn<X{}>());
+ static_assert(!std::invocable<F&>);
+ static_assert(!std::invocable<const F&>);
+ static_assert(!std::invocable<F>);
+ static_assert(!std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is still callable when we delete && overload.
+ struct X {
+ FakeBool operator()() &;
+ FakeBool operator()() const&;
+ FakeBool operator()() && = delete;
+ FakeBool operator()() const&&;
+ };
+
+ using F = decltype(std::not_fn<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is still callable when we delete const&& overload.
+ struct X {
+ FakeBool operator()() &;
+ FakeBool operator()() const&;
+ FakeBool operator()() &&;
+ FakeBool operator()() const&& = delete;
+ };
+
+ using F = decltype(std::not_fn<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+ }
+
+ { // Test perfect forwarding
+ auto f = [](int& val) {
+ val = 5;
+ return false;
+ };
+
+ auto not_f = std::not_fn<f>();
+ int val = 0;
+ assert(not_f(val));
+ assert(val == 5);
+
+ using NotF = decltype(not_f);
+ static_assert(std::invocable<NotF, int&>);
+ static_assert(!std::invocable<NotF, int>);
+ }
+}
+
+constexpr void test_return_type() {
+ { // Test constructors and assignment operators
+ struct IsPowerOfTwo {
+ constexpr bool operator()(unsigned int x) const { return std::has_single_bit(x); }
+ };
+
+ auto is_not_power_of_2 = std::not_fn<IsPowerOfTwo{}>();
+ assert(is_not_power_of_2(5));
+ assert(!is_not_power_of_2(4));
+
+ auto moved = std::move(is_not_power_of_2);
+ assert(moved(5));
+ assert(!moved(4));
+
+ auto copied = is_not_power_of_2;
+ assert(copied(7));
+ assert(!copied(8));
+
+ moved = std::move(copied);
+ assert(copied(9));
+ assert(!copied(16));
+
+ copied = moved;
+ assert(copied(11));
+ assert(!copied(32));
+ }
+
+ { // Make sure `not_fn<NTTP>` unspecified type's operator() is SFINAE-friendly.
+ using F = decltype(std::not_fn<[](int x) { return !x; }>());
+ static_assert(!std::is_invocable<F>::value);
+ static_assert(std::is_invocable<F, int>::value);
+ static_assert(!std::is_invocable<F, void*>::value);
+ static_assert(!std::is_invocable<F, int, int>::value);
+ }
+
+ { // Test noexceptness
+ auto always_noexcept = std::not_fn<MaybeNoexceptFn<true>{}>();
+ static_assert(noexcept(always_noexcept()));
+
+ auto never_noexcept = std::not_fn<MaybeNoexceptFn<false>{}>();
+ static_assert(!noexcept(never_noexcept()));
+ }
+
+ { // Test calling volatile wrapper
+ using NotFn = decltype(std:...
[truncated]
|
✅ With the latest revision this PR passed the Python code formatter. |
332c68c
to
19ea48f
Compare
libcxx/include/__functional/not_fn.h
Outdated
template <auto _Fn> | ||
struct __nttp_not_fn_t { | ||
template <class... _Args> | ||
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const | |
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Args&&... __args) |
I think we can make this operator static
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
volatile
values can be rejected by not using static
, but this doesn't seem worthwhile... See [func.require]/4 and LWG4007.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially, I've made operator()
static but due to the reason previously mentioned
volatile values can be rejected by not using static
I've made it member function again. For now, I'm going to leave it as it is and wait for maintainers' comment.
19ea48f
to
228c8ea
Compare
99f59e4
to
fc15d9f
Compare
Implement `not_fn<NTTP>` function from "P2714R1 Bind front and back to NTTP callables".
fc15d9f
to
a350117
Compare
Implement
not_fn<NTTP>
function from "P2714R1 Bind front and back to NTTP callables".Current implementation does not use
std::__perfect_forward
becausenot_fn<NTTP>
does not have state entities.