[libc++] Implement std::not_fn<NTTP> (#86133) Implement `std::not_fn<NTTP>` from "P2714R1 Bind front and back to NTTP callables". GitOrigin-RevId: c91d805e6627987bec8ec34ea67c1e8240940039
diff --git a/docs/FeatureTestMacroTable.rst b/docs/FeatureTestMacroTable.rst index 3c4a133..cfb0e5c 100644 --- a/docs/FeatureTestMacroTable.rst +++ b/docs/FeatureTestMacroTable.rst
@@ -458,6 +458,8 @@ ---------------------------------------------------------- ----------------- ``__cpp_lib_mdspan`` ``202406L`` ---------------------------------------------------------- ----------------- + ``__cpp_lib_not_fn`` ``202306L`` + ---------------------------------------------------------- ----------------- ``__cpp_lib_optional_range_support`` *unimplemented* ---------------------------------------------------------- ----------------- ``__cpp_lib_out_ptr`` ``202311L``
diff --git a/docs/Status/Cxx2cPapers.csv b/docs/Status/Cxx2cPapers.csv index 3a8a794..aa896e8 100644 --- a/docs/Status/Cxx2cPapers.csv +++ b/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/include/__functional/not_fn.h b/include/__functional/not_fn.h index 4b3ce55..e6f14be 100644 --- a/include/__functional/not_fn.h +++ b/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 @@ #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/include/functional b/include/functional index 4bb163f..b121a19 100644 --- a/include/functional +++ b/include/functional
@@ -214,7 +214,9 @@ 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/include/version b/include/version index 23cffd9..f5b5e7a 100644 --- a/include/version +++ b/include/version
@@ -171,7 +171,8 @@ <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 @@ -557,6 +558,8 @@ // # 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/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp b/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp new file mode 100644 index 0000000..b0128ae --- /dev/null +++ b/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.compile.pass.cpp
@@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// 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; +}; + +bool func(); + +struct SomeClass { + bool member_object; + bool member_function(); +}; + +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 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>);
diff --git a/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp b/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp new file mode 100644 index 0000000..acef2d3 --- /dev/null +++ b/test/libcxx/utilities/function.objects/func.not.fn/not_fn.nttp.nodiscard.verify.cpp
@@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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}} + + auto negated = std::not_fn<F{}>(); + negated(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} +}
diff --git a/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp b/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp index 8ea1934..e19ed32 100644 --- a/test/std/language.support/support.limits/support.limits.general/functional.version.compile.pass.cpp +++ b/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/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp index b0f8b2f..7c03955 100644 --- a/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/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/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp b/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp new file mode 100644 index 0000000..688049c --- /dev/null +++ b/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.pass.cpp
@@ -0,0 +1,310 @@ +//===----------------------------------------------------------------------===// +// +// 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; +}
diff --git a/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.cpp b/test/std/utilities/function.objects/func.not_fn/not_fn.nttp.verify.cpp new file mode 100644 index 0000000..f4ebea7 --- /dev/null +++ b/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/utils/generate_feature_test_macro_components.py b/utils/generate_feature_test_macro_components.py index a2ce698..dae827f 100755 --- a/utils/generate_feature_test_macro_components.py +++ b/utils/generate_feature_test_macro_components.py
@@ -931,7 +931,7 @@ "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"], },