diff options
author | Arthur O'Dwyer <arthur.j.odwyer@gmail.com> | 2021-01-11 16:29:17 -0500 |
---|---|---|
committer | Arthur O'Dwyer <arthur.j.odwyer@gmail.com> | 2021-01-25 19:34:41 -0500 |
commit | f9b6fd269b6fd2b085c3537b6730a02503430ef0 (patch) | |
tree | dfd63821c00a6dc7f46d51dee814c141b0462ebb | |
parent | 70e251497c4e26f8cfd85e745459afff97c909ce (diff) | |
download | llvm-f9b6fd269b6fd2b085c3537b6730a02503430ef0.zip llvm-f9b6fd269b6fd2b085c3537b6730a02503430ef0.tar.gz llvm-f9b6fd269b6fd2b085c3537b6730a02503430ef0.tar.bz2 |
[libc++] Support immovable return types in std::function.
LWG reflector consensus is that this was a bug in libc++.
(In particular, MSVC also will fix it in their STL, soon.)
Bug originally discovered by Logan Smith.
Also fix `std::function<const void()>`, which should work
the same way as `std::function<void()>` in terms of allowing
"conversions" from non-void types.
Differential Revision: https://reviews.llvm.org/D94452
-rw-r--r-- | libcxx/include/__functional_base | 6 | ||||
-rw-r--r-- | libcxx/include/functional | 6 | ||||
-rw-r--r-- | libcxx/include/type_traits | 15 | ||||
-rw-r--r-- | libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp | 138 |
4 files changed, 159 insertions, 6 deletions
diff --git a/libcxx/include/__functional_base b/libcxx/include/__functional_base index 708c1a2..1c02e96 100644 --- a/libcxx/include/__functional_base +++ b/libcxx/include/__functional_base @@ -308,7 +308,7 @@ struct __invoke_return #endif // !defined(_LIBCPP_CXX03_LANG) -template <class _Ret> +template <class _Ret, bool = is_void<_Ret>::value> struct __invoke_void_return_wrapper { #ifndef _LIBCPP_CXX03_LANG @@ -339,8 +339,8 @@ struct __invoke_void_return_wrapper #endif }; -template <> -struct __invoke_void_return_wrapper<void> +template <class _Ret> +struct __invoke_void_return_wrapper<_Ret, true> { #ifndef _LIBCPP_CXX03_LANG template <class ..._Args> diff --git a/libcxx/include/functional b/libcxx/include/functional index 441a741..67baa5b 100644 --- a/libcxx/include/functional +++ b/libcxx/include/functional @@ -2348,9 +2348,9 @@ class _LIBCPP_TEMPLATE_VIS function<_Rp(_ArgTypes...)> template <class _Fp> struct __callable<_Fp, true> { - static const bool value = is_same<void, _Rp>::value || - is_convertible<typename __invoke_of<_Fp, _ArgTypes...>::type, - _Rp>::value; + static const bool value = is_void<_Rp>::value || + __is_core_convertible<typename __invoke_of<_Fp, _ArgTypes...>::type, + _Rp>::value; }; template <class _Fp> struct __callable<_Fp, false> diff --git a/libcxx/include/type_traits b/libcxx/include/type_traits index 48884ea..59dfd1e 100644 --- a/libcxx/include/type_traits +++ b/libcxx/include/type_traits @@ -1665,6 +1665,21 @@ _LIBCPP_INLINE_VAR _LIBCPP_CONSTEXPR bool is_base_of_v = is_base_of<_Bp, _Dp>::value; #endif +// __is_core_convertible + +// [conv.general]/3 says "E is convertible to T" whenever "T t=E;" is well-formed. +// We can't test for that, but we can test implicit convertibility by passing it +// to a function. Notice that __is_core_convertible<void,void> is false, +// and __is_core_convertible<immovable-type,immovable-type> is true in C++17 and later. + +template <class _Tp, class _Up, class = void> +struct __is_core_convertible : public false_type {}; + +template <class _Tp, class _Up> +struct __is_core_convertible<_Tp, _Up, decltype( + static_cast<void(*)(_Up)>(0) ( static_cast<_Tp(*)()>(0)() ) +)> : public true_type {}; + // is_convertible #if __has_feature(is_convertible_to) && !defined(_LIBCPP_USE_IS_CONVERTIBLE_FALLBACK) diff --git a/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp b/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp new file mode 100644 index 0000000..ce84cc1 --- /dev/null +++ b/libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// <functional> + +#include <functional> +#include <cassert> + +#include "test_macros.h" + +// Prevent warning on the `const NonCopyable()` function type. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + +struct NonCopyable { + NonCopyable() = default; + NonCopyable(NonCopyable&&) = delete; + friend bool operator==(NonCopyable, NonCopyable) { return true; } +}; + +struct LargeLambda { + int a[100]; + NonCopyable operator()() const { return NonCopyable(); } + NonCopyable operator()(int) const { return NonCopyable(); } + NonCopyable f() const { return NonCopyable(); } +}; + +void test() +{ + std::function<NonCopyable()> f1a = []() { return NonCopyable(); }; + std::function<NonCopyable()> f2a = +[]() { return NonCopyable(); }; + std::function<NonCopyable()> f3a = LargeLambda(); + std::function<NonCopyable()> f4a = std::ref(f1a); + std::function<NonCopyable(int)> f1b = [](int) { return NonCopyable(); }; + std::function<NonCopyable(int)> f2b = +[](int) { return NonCopyable(); }; + std::function<NonCopyable(int)> f3b = LargeLambda(); + std::function<NonCopyable(int)> f4b = std::ref(f1b); + + assert(f1a() == f2a()); + assert(f3a() == f4a()); + assert(f1b(1) == f2b(1)); + assert(f3b(1) == f4b(1)); +} + +void const_test() +{ + std::function<const NonCopyable()> f1a = []() { return NonCopyable(); }; + std::function<const NonCopyable()> f2a = +[]() { return NonCopyable(); }; + std::function<const NonCopyable()> f3a = LargeLambda(); + std::function<const NonCopyable()> f4a = std::ref(f1a); + std::function<const NonCopyable(int)> f1b = [](int) { return NonCopyable(); }; + std::function<const NonCopyable(int)> f2b = +[](int) { return NonCopyable(); }; + std::function<const NonCopyable(int)> f3b = LargeLambda(); + std::function<const NonCopyable(int)> f4b = std::ref(f1b); + + assert(f1a() == f2a()); + assert(f3a() == f4a()); + assert(f1b(1) == f2b(1)); + assert(f3b(1) == f4b(1)); +} + +void void_test() +{ + std::function<void()> f1a = []() { return NonCopyable(); }; + std::function<void()> f2a = +[]() { return NonCopyable(); }; + std::function<void()> f3a = LargeLambda(); + std::function<void()> f4a = std::ref(f1a); + std::function<void(int)> f1b = [](int) { return NonCopyable(); }; + std::function<void(int)> f2b = +[](int) { return NonCopyable(); }; + std::function<void(int)> f3b = LargeLambda(); + std::function<void(int)> f4b = std::ref(f1b); +} + +void const_void_test() +{ + std::function<const void()> f1a = []() { return NonCopyable(); }; + std::function<const void()> f2a = +[]() { return NonCopyable(); }; + std::function<const void()> f3a = LargeLambda(); + std::function<const void()> f4a = std::ref(f1a); + std::function<const void(int)> f1b = [](int) { return NonCopyable(); }; + std::function<const void(int)> f2b = +[](int) { return NonCopyable(); }; + std::function<const void(int)> f3b = LargeLambda(); + std::function<const void(int)> f4b = std::ref(f1b); +} + +void member_pointer_test() +{ + std::function<NonCopyable(LargeLambda*)> f1a = &LargeLambda::f; + std::function<NonCopyable(LargeLambda&)> f2a = &LargeLambda::f; + LargeLambda ll; + assert(f1a(&ll) == f2a(ll)); + + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)(), std::function<NonCopyable(LargeLambda*)>>); + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)(), std::function<NonCopyable(LargeLambda&)>>); + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(LargeLambda*)>>); + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(LargeLambda&)>>); + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(const LargeLambda*)>>); + static_assert(std::is_convertible_v<NonCopyable (LargeLambda::*)() const, std::function<NonCopyable(const LargeLambda&)>>); + + // Verify we have SFINAE against invoking a pointer-to-data-member in a way that would have to copy the NonCopyable. + static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable(LargeLambda*)>>); + static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable(LargeLambda&)>>); + static_assert(!std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(const LargeLambda&)>>); + static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(LargeLambda*)>>); + static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<NonCopyable&(LargeLambda&)>>); + static_assert(std::is_convertible_v<NonCopyable LargeLambda::*, std::function<const NonCopyable&(const LargeLambda&)>>); +} + +void ctad_test() +{ + std::function f1a = []() { return NonCopyable(); }; + std::function f2a = +[]() { return NonCopyable(); }; + std::function f1b = [](int) { return NonCopyable(); }; + std::function f2b = +[](int) { return NonCopyable(); }; + static_assert(std::is_same_v<decltype(f1a), std::function<NonCopyable()>>); + static_assert(std::is_same_v<decltype(f2a), std::function<NonCopyable()>>); + static_assert(std::is_same_v<decltype(f1b), std::function<NonCopyable(int)>>); + static_assert(std::is_same_v<decltype(f2b), std::function<NonCopyable(int)>>); +} + +int main(int, char**) +{ + test(); + const_test(); + void_test(); + const_void_test(); + member_pointer_test(); + ctad_test(); + return 0; +} |