Jakub Mazurkiewicz c91d805e66
[libc++] Implement std::not_fn<NTTP> (#86133)
Implement `std::not_fn<NTTP>` from "P2714R1 Bind front and back to NTTP callables".
2025-01-10 14:14:14 -05:00

311 lines
9.5 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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
};
template <bool IsNoexcept>
struct MaybeNoexceptNegation {
bool operator!() noexcept(IsNoexcept); // not defined
};
template <bool IsNoexcept>
MaybeNoexceptNegation<IsNoexcept> maybe_noexcept_negation() noexcept {
return {};
}
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 the const& overload of the underlying function object.
{
{ // Make sure unspecified-type is still callable when we delete the & 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 the 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 the && 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 the 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()));
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
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;
}