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

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Nov 28 12:28:16 PST 2024


================
@@ -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.
----------------
ldionne wrote:

```suggestion
    { // Make sure unspecified-type is still callable when we delete the & overload.
```

Throughout below.

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


More information about the libcxx-commits mailing list