[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
This commit is contained in:
Arthur O'Dwyer 2021-01-11 16:29:17 -05:00
parent 70e251497c
commit f9b6fd269b
4 changed files with 159 additions and 6 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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;
}