311 lines
9.5 KiB
C++
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;
|
|
}
|