[libcxx-commits] [libcxx] [libc++][functional] Implement `not_fn<NTTP>` (PR #86133)

Jakub Mazurkiewicz via libcxx-commits libcxx-commits at lists.llvm.org
Mon Dec 9 00:51:11 PST 2024


https://github.com/JMazurkiewicz updated https://github.com/llvm/llvm-project/pull/86133

>From 009bdc4ef86c8007b35b874d375c153592e9678b Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Tue, 19 Mar 2024 21:08:30 +0100
Subject: [PATCH 1/7] [libc++] Implement `not_fn<NTTP>`

Implement `not_fn<NTTP>` function from "P2714R1 Bind front and back to NTTP callables".
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/__functional/not_fn.h          |  23 ++
 libcxx/include/functional                     |   4 +-
 libcxx/include/version                        |   5 +-
 .../func.not.fn/not_fn.nttp.compile.pass.cpp  |  29 ++
 .../not_fn.nttp.nodiscard.verify.cpp          |  21 ++
 .../functional.version.compile.pass.cpp       |   5 +-
 .../version.version.compile.pass.cpp          |   5 +-
 .../func.not_fn/not_fn.nttp.pass.cpp          | 294 ++++++++++++++++++
 .../func.not_fn/not_fn.nttp.verify.cpp        |  29 ++
 .../generate_feature_test_macro_components.py |   2 +-
 12 files changed, 413 insertions(+), 8 deletions(-)
 create mode 100644 libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
 create mode 100644 libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp
 create mode 100644 libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 3c4a13332661ee..cfb0e5cfb129ce 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -458,6 +458,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_mdspan``                                       ``202406L``
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_not_fn``                                       ``202306L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_optional_range_support``                       *unimplemented*
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_out_ptr``                                      ``202311L``
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 3a8a794ca4ea1e..aa896e85fcb1fe 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -24,7 +24,7 @@
 "`P1383R2 <https://wg21.link/P1383R2>`__","More ``constexpr`` for ``<cmath>`` and ``<complex>``","2023-06 (Varna)","","",""
 "`P2734R0 <https://wg21.link/P2734R0>`__","Adding the new SI prefixes","2023-06 (Varna)","|Complete|","17",""
 "`P2548R6 <https://wg21.link/P2548R6>`__","``copyable_function``","2023-06 (Varna)","","",""
-"`P2714R1 <https://wg21.link/P2714R1>`__","Bind front and back to NTTP callables","2023-06 (Varna)","","",""
+"`P2714R1 <https://wg21.link/P2714R1>`__","Bind front and back to NTTP callables","2023-06 (Varna)","|Partial|","20","``not_fn`` only"
 "`P2630R4 <https://wg21.link/P2630R4>`__","``submdspan``","2023-06 (Varna)","","",""
 "","","","","",""
 "`P0543R3 <https://wg21.link/P0543R3>`__","Saturation arithmetic","2023-11 (Kona)","|Complete|","18",""
diff --git a/libcxx/include/__functional/not_fn.h b/libcxx/include/__functional/not_fn.h
index 4b3ce5524a7434..e6f14be799db33 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>
+  [[__nodiscard__]] _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>
+[[__nodiscard__]] _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 55b50eea84b732..8284d3033c8e78 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -214,7 +214,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
 
 // [func.bind.partial], function templates bind_front and bind_back
 template<class F, class... Args>
diff --git a/libcxx/include/version b/libcxx/include/version
index fc57aeade9daf2..984bcbf6feca19 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -171,7 +171,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>
                                                         202106L // C++20
@@ -554,6 +555,8 @@ __cpp_lib_void_t                                        201411L <type_traits>
 // # define __cpp_lib_linalg                               202311L
 # undef  __cpp_lib_mdspan
 # define __cpp_lib_mdspan                               202406L
+# undef  __cpp_lib_not_fn
+# define __cpp_lib_not_fn                               202306L
 // # define __cpp_lib_optional_range_support               202406L
 # undef  __cpp_lib_out_ptr
 # define __cpp_lib_out_ptr                              202311L
diff --git a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
new file mode 100644
index 00000000000000..407db07c57804d
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.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>
+
+// Type of `std::not_fn<NTTP>()` is always empty.
+
+#include <functional>
+#include <type_traits>
+
+struct NonEmptyFunctionObject {
+  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<NonEmptyFunctionObject{}>());
+  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 8ea19345903630..e19ed321515ecb 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
@@ -27,6 +27,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                   202110L [C++20]
                                        202406L [C++23]
     __cpp_lib_reference_wrapper        202403L [C++26]
@@ -525,8 +526,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 b0f8b2f80067d5..7c03955df681d5 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
@@ -156,6 +156,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]
                                                             202106L [C++20]
@@ -7405,8 +7406,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::not_fn<std::false_type{}>());
+    static_assert(!std::invocable<volatile NotFn&>);
+    static_assert(!std::invocable<const volatile NotFn&>);
+    static_assert(!std::invocable<volatile NotFn>);
+    static_assert(!std::invocable<const volatile NotFn>);
+  }
+}
+
+constexpr bool test() {
+  basic_tests();
+  test_perfect_forwarding_call_wrapper();
+  test_return_type();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.cpp b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.cpp
new file mode 100644
index 00000000000000..f4ebea7e8927eb
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.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>
+
+// template<auto f> constexpr unspecified not_fn() noexcept;
+// Mandates: If is_pointer_v<F> || is_member_pointer_v<F> is true, then f != nullptr is true.
+
+#include <functional>
+
+struct X {};
+
+void test() {
+  auto not_fn1 = std::not_fn<static_cast<bool (*)()>(nullptr)>();
+  // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+
+  auto not_fn2 = std::not_fn<static_cast<bool X::*>(nullptr)>();
+  // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+
+  auto not_fn3 = std::not_fn<static_cast<bool (X::*)()>(nullptr)>();
+  // expected-error@*:* {{static assertion failed due to requirement 'nullptr != nullptr': f cannot be equal to nullptr}}
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 53fd44291b216a..9b14b6092d94ff 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -931,7 +931,7 @@ def add_version_header(tc):
             "name": "__cpp_lib_not_fn",
             "values": {
                 "c++17": 201603,
-                # "c++26": 202306, # P2714R1 Bind front and back to NTTP callables
+                "c++26": 202306,  # P2714R1 Bind front and back to NTTP callables
             },
             "headers": ["functional"],
         },

>From 7ea0b51fa64fc74f7b2b50d8e614ad6fecbc19ed Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 00:21:50 +0100
Subject: [PATCH 2/7] Move assertions to file scope

Comment: https://github.com/llvm/llvm-project/pull/86133#discussion_r1862658517
---
 .../func.not.fn/not_fn.nttp.compile.pass.cpp           | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
index 407db07c57804d..6f31a4463c4ae7 100644
--- a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
@@ -20,10 +20,8 @@ struct NonEmptyFunctionObject {
   bool operator()() const; // not defined
 };
 
-void test() {
-  using ResultWithEmptyFuncObject = decltype(std::not_fn<std::false_type{}>());
-  static_assert(std::is_empty_v<ResultWithEmptyFuncObject>);
+using ResultWithEmptyFuncObject = decltype(std::not_fn<std::false_type{}>());
+static_assert(std::is_empty_v<ResultWithEmptyFuncObject>);
 
-  using ResultWithNotEmptyFuncObject = decltype(std::not_fn<NonEmptyFunctionObject{}>());
-  static_assert(std::is_empty_v<ResultWithNotEmptyFuncObject>);
-}
+using ResultWithNotEmptyFuncObject = decltype(std::not_fn<NonEmptyFunctionObject{}>());
+static_assert(std::is_empty_v<ResultWithNotEmptyFuncObject>);

>From 01540ec68118d5729d6e1378859fa871508fe2ec Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 01:37:05 +0100
Subject: [PATCH 3/7] Test more `not_fn`s for emptiness

Comment: https://github.com/llvm/llvm-project/pull/86133#discussion_r1862659285
---
 .../func.not.fn/not_fn.nttp.compile.pass.cpp   | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
index 6f31a4463c4ae7..b0128ae6ff0530 100644
--- a/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
@@ -17,7 +17,14 @@
 
 struct NonEmptyFunctionObject {
   bool val = true;
-  bool operator()() const; // not defined
+  bool operator()() const;
+};
+
+bool func();
+
+struct SomeClass {
+  bool member_object;
+  bool member_function();
 };
 
 using ResultWithEmptyFuncObject = decltype(std::not_fn<std::false_type{}>());
@@ -25,3 +32,12 @@ static_assert(std::is_empty_v<ResultWithEmptyFuncObject>);
 
 using ResultWithNotEmptyFuncObject = decltype(std::not_fn<NonEmptyFunctionObject{}>());
 static_assert(std::is_empty_v<ResultWithNotEmptyFuncObject>);
+
+using ResultWithFunctionPointer = decltype(std::not_fn<&func>());
+static_assert(std::is_empty_v<ResultWithFunctionPointer>);
+
+using ResultWithMemberObjectPointer = decltype(std::not_fn<&SomeClass::member_object>());
+static_assert(std::is_empty_v<ResultWithMemberObjectPointer>);
+
+using ResultWithMemberFunctionPointer = decltype(std::not_fn<&SomeClass::member_function>());
+static_assert(std::is_empty_v<ResultWithMemberFunctionPointer>);

>From bdfe173e190af38d21d3075acca7c47675a0dfb9 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 01:41:37 +0100
Subject: [PATCH 4/7] Add missing `[[nodiscard]]` test

Comment: https://github.com/llvm/llvm-project/pull/86133#discussion_r1862660535
---
 .../func.not.fn/not_fn.nttp.nodiscard.verify.cpp               | 3 +++
 1 file changed, 3 insertions(+)

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
index 50d956e73fc09d..acef2d3d24a29d 100644
--- 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
@@ -18,4 +18,7 @@
 void test() {
   using F = std::true_type;
   std::not_fn<F{}>(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+
+  auto negated = std::not_fn<F{}>();
+  negated(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
 }

>From d92507c5efc12cb87448317804f4d453c8c70ded Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 01:44:26 +0100
Subject: [PATCH 5/7] the

Comment: https://github.com/llvm/llvm-project/pull/86133#discussion_r1862662233
---
 .../function.objects/func.not_fn/not_fn.nttp.pass.cpp     | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

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
index 29df597704e1e8..d876646236951d 100644
--- 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
@@ -148,7 +148,7 @@ constexpr void test_perfect_forwarding_call_wrapper() {
 
   // 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.
+    { // Make sure unspecified-type is still callable when we delete the & overload.
       struct X {
         FakeBool operator()() & = delete;
         FakeBool operator()() const&;
@@ -163,7 +163,7 @@ constexpr void test_perfect_forwarding_call_wrapper() {
       static_assert(std::invocable<const F>);
     }
 
-    { // Make sure unspecified-type is not callable when we delete const& overload.
+    { // Make sure unspecified-type is not callable when we delete the const& overload.
       struct X {
         FakeBool operator()() &;
         FakeBool operator()() const& = delete;
@@ -178,7 +178,7 @@ constexpr void test_perfect_forwarding_call_wrapper() {
       static_assert(!std::invocable<const F>);
     }
 
-    { // Make sure unspecified-type is still callable when we delete && overload.
+    { // Make sure unspecified-type is still callable when we delete the && overload.
       struct X {
         FakeBool operator()() &;
         FakeBool operator()() const&;
@@ -193,7 +193,7 @@ constexpr void test_perfect_forwarding_call_wrapper() {
       static_assert(std::invocable<const F>);
     }
 
-    { // Make sure unspecified-type is still callable when we delete const&& overload.
+    { // Make sure unspecified-type is still callable when we delete the const&& overload.
       struct X {
         FakeBool operator()() &;
         FakeBool operator()() const&;

>From e045e2e6f1437548aa1459c28f8fbd5454093996 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 02:57:28 +0100
Subject: [PATCH 6/7] Add extra `noexcept` test

---
 .../func.not_fn/not_fn.nttp.pass.cpp               | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

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
index d876646236951d..8adc3492b7a4fe 100644
--- 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
@@ -47,6 +47,14 @@ struct MaybeNoexceptFn {
   bool operator()() const noexcept(IsNoexcept); // not defined
 };
 
+template <bool IsNoexcept>
+struct MaybeNoexceptNegation {
+  bool operator!() noexcept(IsNoexcept); // not defined
+};
+
+template <bool IsNoexcept>
+MaybeNoexceptNegation<IsNoexcept> maybe_noexcept_negation() noexcept; // not defined
+
 constexpr void basic_tests() {
   { // Test constant functions
     auto false_fn = std::not_fn<std::false_type{}>();
@@ -267,6 +275,12 @@ constexpr void test_return_type() {
 
     auto never_noexcept = std::not_fn<MaybeNoexceptFn<false>{}>();
     static_assert(!noexcept(never_noexcept()));
+
+    auto always_noexcept_negation = std::not_fn<maybe_noexcept_negation<true>>();
+    static_assert(noexcept(always_noexcept_negation()));
+
+    auto never_noexcept_negation = std::not_fn<maybe_noexcept_negation<false>>();
+    static_assert(!noexcept(never_noexcept_negation()));
   }
 
   { // Test calling volatile wrapper

>From e2587ffdc4b57dc390b7ba208c13015bde076913 Mon Sep 17 00:00:00 2001
From: Jakub Mazurkiewicz <mazkuba3 at gmail.com>
Date: Fri, 29 Nov 2024 03:19:07 +0100
Subject: [PATCH 7/7] Finish sentence

Comment: https://github.com/llvm/llvm-project/pull/86133#discussion_r1862662065
---
 .../function.objects/func.not_fn/not_fn.nttp.pass.cpp         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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
index 8adc3492b7a4fe..24782992a6601f 100644
--- 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
@@ -154,7 +154,7 @@ constexpr void test_perfect_forwarding_call_wrapper() {
     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 .
+  // Call to `not_fn<NTTP>` unspecified-type's operator() should always result in call to the const& overload of the underlying function object.
   {
     { // Make sure unspecified-type is still callable when we delete the & overload.
       struct X {
@@ -261,7 +261,7 @@ constexpr void test_return_type() {
     assert(!copied(32));
   }
 
-  { // Make sure `not_fn<NTTP>` unspecified type's operator() is SFINAE-friendly.
+  { // 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);



More information about the libcxx-commits mailing list