From 5725756791452eb91e23e37cb9958687771cb10a Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Thu, 2 Jun 2016 01:25:41 +0000 Subject: [PATCH] Add C++17 std::not_fn negator. Summary: Exactly what it sounds like. I plan to commit this in a couple of days assuming no objections. Reviewers: mclow.lists, EricWF Subscribers: cfe-commits Differential Revision: http://reviews.llvm.org/D20799 llvm-svn: 271464 --- libcxx/include/functional | 49 +- .../func.invoke/invoke.pass.cpp | 38 ++ .../func.not_fn/not_fn.pass.cpp | 540 ++++++++++++++++++ libcxx/www/cxx1z_status.html | 4 +- 4 files changed, 627 insertions(+), 4 deletions(-) create mode 100644 libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.pass.cpp diff --git a/libcxx/include/functional b/libcxx/include/functional index 4fcd4b5a7aca..9e4d5db41629 100644 --- a/libcxx/include/functional +++ b/libcxx/include/functional @@ -207,6 +207,8 @@ public: template binary_negate not2(const Predicate& pred); +template unspecified not_fn(F&& f); // C++17 + template struct is_bind_expression; template struct is_placeholder; @@ -2585,11 +2587,54 @@ struct _LIBCPP_TYPE_VIS_ONLY hash #if _LIBCPP_STD_VER > 14 + template result_of_t<_Fn&&(_Args&&...)> -invoke(_Fn&& __f, _Args&&... __args) { - return __invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)...); +invoke(_Fn&& __f, _Args&&... __args) + noexcept(noexcept(_VSTD::__invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)...))) +{ + return _VSTD::__invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)...); } + +template +class _LIBCPP_TYPE_VIS_ONLY __not_fn_imp { + _DecayFunc __fd; + +public: + __not_fn_imp() = delete; + + template + _LIBCPP_INLINE_VISIBILITY + auto operator()(_Args&& ...__args) + noexcept(noexcept(!_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...))) + -> decltype(!_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...)) + { return !_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...); } + + template + _LIBCPP_INLINE_VISIBILITY + auto operator()(_Args&& ...__args) const + noexcept(noexcept(!_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...))) + -> decltype(!_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...)) + { return !_VSTD::invoke(__fd, _VSTD::forward<_Args>(__args)...); } + +private: + template , __not_fn_imp>::value>> + _LIBCPP_INLINE_VISIBILITY + explicit __not_fn_imp(_RawFunc&& __rf) + : __fd(_VSTD::forward<_RawFunc>(__rf)) {} + + template + friend inline _LIBCPP_INLINE_VISIBILITY + __not_fn_imp> not_fn(_RawFunc&&); +}; + +template +inline _LIBCPP_INLINE_VISIBILITY +__not_fn_imp> not_fn(_RawFunc&& __fn) { + return __not_fn_imp>(_VSTD::forward<_RawFunc>(__fn)); +} + #endif // struct hash in diff --git a/libcxx/test/std/utilities/function.objects/func.invoke/invoke.pass.cpp b/libcxx/test/std/utilities/function.objects/func.invoke/invoke.pass.cpp index 3f02c7c4c81e..e78323f5ae80 100644 --- a/libcxx/test/std/utilities/function.objects/func.invoke/invoke.pass.cpp +++ b/libcxx/test/std/utilities/function.objects/func.invoke/invoke.pass.cpp @@ -304,8 +304,46 @@ void bullet_five_tests() { } } +struct CopyThrows { + CopyThrows() {} + CopyThrows(CopyThrows const&) {} + CopyThrows(CopyThrows&&) noexcept {} +}; + +struct NoThrowCallable { + void operator()() noexcept {} + void operator()(CopyThrows) noexcept {} +}; + +struct ThrowsCallable { + void operator()() {} +}; + +struct MemberObj { + int x; +}; + +void noexcept_test() { + { + NoThrowCallable obj; + CopyThrows arg; + static_assert(noexcept(std::invoke(obj))); + static_assert(!noexcept(std::invoke(obj, arg))); + static_assert(noexcept(std::invoke(obj, std::move(arg)))); + } + { + ThrowsCallable obj; + static_assert(!noexcept(std::invoke(obj))); + } + { + MemberObj obj{42}; + static_assert(noexcept(std::invoke(&MemberObj::x, obj))); + } +} + int main() { bullet_one_two_tests(); bullet_three_four_tests(); bullet_five_tests(); + noexcept_test(); } diff --git a/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.pass.cpp b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.pass.cpp new file mode 100644 index 000000000000..a338388fe7af --- /dev/null +++ b/libcxx/test/std/utilities/function.objects/func.not_fn/not_fn.pass.cpp @@ -0,0 +1,540 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03, c++11, c++14 + +// template unspecified not_fn(F&& f); + +#include +#include +#include +#include + +#include "test_macros.h" +#include "type_id.h" + + +/////////////////////////////////////////////////////////////////////////////// +// CALLABLE TEST TYPES +/////////////////////////////////////////////////////////////////////////////// + +bool returns_true() { return true; } + +template +struct MoveOnlyCallable { + MoveOnlyCallable(MoveOnlyCallable const&) = delete; + MoveOnlyCallable(MoveOnlyCallable&& other) + : value(other.value) + { other.value = !other.value; } + + template + Ret operator()(Args&&...) { return Ret{value}; } + + explicit MoveOnlyCallable(bool x) : value(x) {} + Ret value; +}; + +template +struct CopyCallable { + CopyCallable(CopyCallable const& other) + : value(other.value) {} + + CopyCallable(CopyCallable&& other) + : value(other.value) { other.value = !other.value; } + + template + Ret operator()(Args&&...) { return Ret{value}; } + + explicit CopyCallable(bool x) : value(x) {} + Ret value; +}; + + +template +struct ConstCallable { + ConstCallable(ConstCallable const& other) + : value(other.value) {} + + ConstCallable(ConstCallable&& other) + : value(other.value) { other.value = !other.value; } + + template + Ret operator()(Args&&...) const { return Ret{value}; } + + explicit ConstCallable(bool x) : value(x) {} + Ret value; +}; + + + +template +struct NoExceptCallable { + NoExceptCallable(NoExceptCallable const& other) + : value(other.value) {} + + template + Ret operator()(Args&&...) noexcept { return Ret{value}; } + + template + Ret operator()(Args&&...) const noexcept { return Ret{value}; } + + explicit NoExceptCallable(bool x) : value(x) {} + Ret value; +}; + + +struct CopyAssignableWrapper { + CopyAssignableWrapper(CopyAssignableWrapper const&) = default; + CopyAssignableWrapper(CopyAssignableWrapper&&) = default; + CopyAssignableWrapper& operator=(CopyAssignableWrapper const&) = default; + CopyAssignableWrapper& operator=(CopyAssignableWrapper &&) = default; + + template + bool operator()(Args&&...) { return value; } + + explicit CopyAssignableWrapper(bool x) : value(x) {} + bool value; +}; + + +struct MoveAssignableWrapper { + MoveAssignableWrapper(MoveAssignableWrapper const&) = delete; + MoveAssignableWrapper(MoveAssignableWrapper&&) = default; + MoveAssignableWrapper& operator=(MoveAssignableWrapper const&) = delete; + MoveAssignableWrapper& operator=(MoveAssignableWrapper &&) = default; + + template + bool operator()(Args&&...) { return value; } + + explicit MoveAssignableWrapper(bool x) : value(x) {} + bool value; +}; + +struct MemFunCallable { + explicit MemFunCallable(bool x) : value(x) {} + + bool return_value() const { return value; } + bool return_value_nc() { return value; } + bool value; +}; + +enum CallType { + CT_None, + CT_Const, + CT_NonConst +}; + +struct ForwardingCallObject { + + template + bool operator()(Args&&... args) & { + set_call(CT_NonConst); + return true; + } + + template + bool operator()(Args&&... args) const & { + set_call(CT_Const); + return true; + } + + // Don't allow the call operator to be invoked as an rvalue. + template + bool operator()(Args&&... args) && = delete; + + template + bool operator()(Args&&... args) const && = delete; + + template + static void set_call(CallType type) { + assert(last_call_type == CT_None); + assert(last_call_args == nullptr); + last_call_type = type; + last_call_args = &makeArgumentID(); + } + + template + static bool check_call(CallType type) { + bool result = + last_call_type == type + && last_call_args + && *last_call_args == makeArgumentID(); + last_call_type = CT_None; + last_call_args = nullptr; + return result; + } + + static CallType last_call_type; + static TypeID const* last_call_args; +}; + +CallType ForwardingCallObject::last_call_type = CT_None; +TypeID const* ForwardingCallObject::last_call_args = nullptr; + + + +/////////////////////////////////////////////////////////////////////////////// +// BOOL TEST TYPES +/////////////////////////////////////////////////////////////////////////////// + +struct EvilBool { + static int bang_called; + + EvilBool(EvilBool const&) = default; + EvilBool(EvilBool&&) = default; + + friend EvilBool operator!(EvilBool const& other) { + ++bang_called; + return EvilBool{!other.value}; + } + +private: + friend struct MoveOnlyCallable; + friend struct CopyCallable; + friend struct NoExceptCallable; + + explicit EvilBool(bool x) : value(x) {} + EvilBool& operator=(EvilBool const& other) = default; + +public: + bool value; +}; + +int EvilBool::bang_called = 0; + +struct ExplicitBool { + ExplicitBool(ExplicitBool const&) = default; + ExplicitBool(ExplicitBool&&) = default; + + explicit operator bool() const { return value; } + +private: + friend struct MoveOnlyCallable; + friend struct CopyCallable; + + explicit ExplicitBool(bool x) : value(x) {} + ExplicitBool& operator=(bool x) { + value = x; + return *this; + } + + bool value; +}; + + +struct NoExceptEvilBool { + NoExceptEvilBool(NoExceptEvilBool const&) = default; + NoExceptEvilBool(NoExceptEvilBool&&) = default; + NoExceptEvilBool& operator=(NoExceptEvilBool const& other) = default; + + explicit NoExceptEvilBool(bool x) : value(x) {} + + friend NoExceptEvilBool operator!(NoExceptEvilBool const& other) noexcept { + return NoExceptEvilBool{!other.value}; + } + + bool value; +}; + + + +void constructor_tests() +{ + { + using T = MoveOnlyCallable; + T value(true); + using RetT = decltype(std::not_fn(std::move(value))); + static_assert(std::is_move_constructible::value); + static_assert(!std::is_copy_constructible::value); + static_assert(!std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + auto ret = std::not_fn(std::move(value)); + // test it was moved from + assert(value.value == false); + // test that ret() negates the original value 'true' + assert(ret() == false); + assert(ret(0, 0.0, "blah") == false); + // Move ret and test that it was moved from and that ret2 got the + // original value. + auto ret2 = std::move(ret); + assert(ret() == true); + assert(ret2() == false); + assert(ret2(42) == false); + } + { + using T = CopyCallable; + T value(false); + using RetT = decltype(std::not_fn(value)); + static_assert(std::is_move_constructible::value); + static_assert(std::is_copy_constructible::value); + static_assert(!std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + auto ret = std::not_fn(value); + // test that value is unchanged (copied not moved) + assert(value.value == false); + // test 'ret' has the original value + assert(ret() == true); + assert(ret(42, 100) == true); + // move from 'ret' and check that 'ret2' has the original value. + auto ret2 = std::move(ret); + assert(ret() == false); + assert(ret2() == true); + assert(ret2("abc") == true); + } + { + using T = CopyAssignableWrapper; + T value(true); + T value2(false); + using RetT = decltype(std::not_fn(value)); + static_assert(std::is_move_constructible::value); + static_assert(std::is_copy_constructible::value); + static_assert(std::is_move_assignable::value); + static_assert(std::is_copy_assignable::value); + auto ret = std::not_fn(value); + assert(ret() == false); + auto ret2 = std::not_fn(value2); + assert(ret2() == true); + ret = ret2; + assert(ret() == true); + assert(ret2() == true); + } + { + using T = MoveAssignableWrapper; + T value(true); + T value2(false); + using RetT = decltype(std::not_fn(std::move(value))); + static_assert(std::is_move_constructible::value); + static_assert(!std::is_copy_constructible::value); + static_assert(std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + auto ret = std::not_fn(std::move(value)); + assert(ret() == false); + auto ret2 = std::not_fn(std::move(value2)); + assert(ret2() == true); + ret = std::move(ret2); + assert(ret() == true); + } +} + +void return_type_tests() +{ + using std::is_same; + { + using T = CopyCallable; + auto ret = std::not_fn(T{false}); + static_assert(is_same::value); + static_assert(is_same::value); + assert(ret() == true); + } + { + using T = CopyCallable; + auto ret = std::not_fn(T{true}); + static_assert(is_same::value); + static_assert(is_same::value); + assert(ret() == false); + } + { + using T = CopyCallable; + auto ret = std::not_fn(T{false}); + static_assert(is_same::value); + EvilBool::bang_called = 0; + auto value_ret = ret(); + assert(EvilBool::bang_called == 1); + assert(value_ret.value == true); + ret(); + assert(EvilBool::bang_called == 2); + } +} + +// Other tests only test using objects with call operators. Test various +// other callable types here. +void other_callable_types_test() +{ + { // test with function pointer + auto ret = std::not_fn(returns_true); + assert(ret() == false); + } + { // test with lambda + auto returns_value = [](bool value) { return value; }; + auto ret = std::not_fn(returns_value); + assert(ret(true) == false); + assert(ret(false) == true); + } + { // test with pointer to member function + MemFunCallable mt(true); + const MemFunCallable mf(false); + auto ret = std::not_fn(&MemFunCallable::return_value); + assert(ret(mt) == false); + assert(ret(mf) == true); + assert(ret(&mt) == false); + assert(ret(&mf) == true); + } + { // test with pointer to member function + MemFunCallable mt(true); + MemFunCallable mf(false); + auto ret = std::not_fn(&MemFunCallable::return_value_nc); + assert(ret(mt) == false); + assert(ret(mf) == true); + assert(ret(&mt) == false); + assert(ret(&mf) == true); + } + { // test with pointer to member data + MemFunCallable mt(true); + const MemFunCallable mf(false); + auto ret = std::not_fn(&MemFunCallable::value); + assert(ret(mt) == false); + assert(ret(mf) == true); + assert(ret(&mt) == false); + assert(ret(&mf) == true); + } +} + +void throws_in_constructor_test() +{ +#ifndef TEST_HAS_NO_EXCEPTIONS + struct ThrowsOnCopy { + ThrowsOnCopy(ThrowsOnCopy const&) { + throw 42; + } + ThrowsOnCopy() = default; + bool operator()() const { assert(false); } + }; + { + ThrowsOnCopy cp; + try { + std::not_fn(cp); + assert(false); + } catch (int const& value) { + assert(value == 42); + } + } +#endif +} + +void call_operator_sfinae_test() { + { // wrong number of arguments + using T = decltype(std::not_fn(returns_true)); + static_assert(std::is_callable::value); // callable only with no args + static_assert(!std::is_callable::value); + } + { // violates const correctness (member function pointer) + using T = decltype(std::not_fn(&MemFunCallable::return_value_nc)); + static_assert(std::is_callable::value); + static_assert(!std::is_callable::value); + } + { // violates const correctness (call object) + using Obj = CopyCallable; + using NCT = decltype(std::not_fn(Obj{true})); + using CT = const NCT; + static_assert(std::is_callable::value); + static_assert(!std::is_callable::value); + } + { // returns bad type with no operator! + auto fn = [](auto x) { return x; }; + using T = decltype(std::not_fn(fn)); + static_assert(std::is_callable::value); + static_assert(!std::is_callable::value); + } +} + +void call_operator_forwarding_test() +{ + using Fn = ForwardingCallObject; + auto obj = std::not_fn(Fn{}); + const auto& c_obj = obj; + { // test zero args + obj(); + assert(Fn::check_call<>(CT_NonConst)); + c_obj(); + assert(Fn::check_call<>(CT_Const)); + } + { // test value categories + int x = 42; + const int cx = 42; + obj(x); + assert(Fn::check_call(CT_NonConst)); + obj(cx); + assert(Fn::check_call(CT_NonConst)); + obj(std::move(x)); + assert(Fn::check_call(CT_NonConst)); + obj(std::move(cx)); + assert(Fn::check_call(CT_NonConst)); + obj(42); + assert(Fn::check_call(CT_NonConst)); + } + { // test value categories - const call + int x = 42; + const int cx = 42; + c_obj(x); + assert(Fn::check_call(CT_Const)); + c_obj(cx); + assert(Fn::check_call(CT_Const)); + c_obj(std::move(x)); + assert(Fn::check_call(CT_Const)); + c_obj(std::move(cx)); + assert(Fn::check_call(CT_Const)); + c_obj(42); + assert(Fn::check_call(CT_Const)); + } + { // test multi arg + int x = 42; + const double y = 3.14; + std::string s = "abc"; + obj(42, std::move(y), s, std::string{"foo"}); + Fn::check_call(CT_NonConst); + c_obj(42, std::move(y), s, std::string{"foo"}); + Fn::check_call(CT_Const); + } + { // call as rvalue test. This should not invoke the functor as an rvalue. + std::move(obj)(); + assert(Fn::check_call<>(CT_NonConst)); + std::move(c_obj)(); + assert(Fn::check_call<>(CT_Const)); + } +} + +void call_operator_noexcept_test() +{ + { + using T = ConstCallable; + T value(true); + auto ret = std::not_fn(value); + static_assert(!noexcept(ret()), "call should not be noexcept"); + auto const& cret = ret; + static_assert(!noexcept(cret()), "call should not be noexcept"); + } + { + using T = NoExceptCallable; + T value(true); + auto ret = std::not_fn(value); + static_assert(noexcept(!_VSTD::__invoke(value)), ""); + static_assert(noexcept(ret()), "call should be noexcept"); + auto const& cret = ret; + static_assert(noexcept(cret()), "call should be noexcept"); + } + { + using T = NoExceptCallable; + T value(true); + auto ret = std::not_fn(value); + static_assert(!noexcept(ret()), "call should not be noexcept"); + auto const& cret = ret; + static_assert(!noexcept(cret()), "call should not be noexcept"); + } +} + +int main() +{ + constructor_tests(); + return_type_tests(); + other_callable_types_test(); + throws_in_constructor_test(); + call_operator_sfinae_test(); // somewhat of an extension + call_operator_forwarding_test(); + call_operator_noexcept_test(); +} diff --git a/libcxx/www/cxx1z_status.html b/libcxx/www/cxx1z_status.html index 68d15e87eaf1..e0150a014f9c 100644 --- a/libcxx/www/cxx1z_status.html +++ b/libcxx/www/cxx1z_status.html @@ -81,9 +81,9 @@ P0024R2LWGThe Parallelism TS Should be StandardizedJacksonville P0226R1LWGMathematical Special Functions for C++17Jacksonville P0220R1LWGAdopt Library Fundamentals V1 TS Components for C++17Jacksonville - P0218R1LWGAdopt the File System TS for C++17Jacksonville + P0218R1LWGAdopt the File System TS for C++17Jacksonville P0033R1LWGRe-enabling shared_from_thisJacksonvilleComplete3.9 - P0005R4LWGAdopt not_fn from Library Fundamentals 2 for C++17Jacksonville + P0005R4LWGAdopt not_fn from Library Fundamentals 2 for C++17JacksonvilleComplete3.9 P0152R1LWGconstexpr atomic::is_always_lock_freeJacksonvilleComplete3.9 P0185R1LWGAdding [nothrow-]swappable traitsJacksonvilleComplete3.9 P0253R1LWGFixing a design mistake in the searchers interfaceJacksonvilleComplete3.9