forked from OSchip/llvm-project
[libc++] type_traits: use __is_core_convertible in __invokable_r.
This fixes incorrect handling of non-moveable types, adding tests for this case. See [issue 55346](https://github.com/llvm/llvm-project/issues/55346). The current implementation is based on is_convertible, which is [defined](https://timsong-cpp.github.io/cppwp/n4659/meta.rel#5) in terms of validity of the following function: ``` To test() { return declval<From>(); } ``` But this doesn't work if To and From are both some non-moveable type, which the [definition](https://timsong-cpp.github.io/cppwp/n4659/conv#3) of implicit conversions says should work due to guaranteed copy elision: ``` To to = E; // E has type From ``` It is this latter definition that is used in the [definition](https://timsong-cpp.github.io/cppwp/n4659/function.objects#func.require-2) of INVOKE<R>. Make __invokable_r use __is_core_convertible, which captures the ability to use guaranteed copy elision, making the definition correct for non-moveable types. Fixes llvm/llvm-project#55346. Reviewed By: #libc, philnik, EricWF Spies: EricWF, jloser, ldionne, philnik, libcxx-commits Differential Revision: https://reviews.llvm.org/D125300
This commit is contained in:
parent
c0e06c7448
commit
c3a2488290
|
@ -2992,16 +2992,10 @@ struct __invokable_r
|
|||
// or incomplete array types as required by the standard.
|
||||
using _Result = decltype(__try_call<_Fp, _Args...>(0));
|
||||
|
||||
using type =
|
||||
typename conditional<
|
||||
using type = typename conditional<
|
||||
_IsNotSame<_Result, __nat>::value,
|
||||
typename conditional<
|
||||
is_void<_Ret>::value,
|
||||
true_type,
|
||||
is_convertible<_Result, _Ret>
|
||||
>::type,
|
||||
false_type
|
||||
>::type;
|
||||
typename conditional< is_void<_Ret>::value, true_type, __is_core_convertible<_Result, _Ret> >::type,
|
||||
false_type >::type;
|
||||
static const bool value = type::value;
|
||||
};
|
||||
template <class _Fp, class ..._Args>
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
|
||||
// is_invocable_r
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
// Non-invocable types
|
||||
|
||||
static_assert(!std::is_invocable_r<void, void>::value);
|
||||
static_assert(!std::is_invocable_r<void, int>::value);
|
||||
static_assert(!std::is_invocable_r<void, int*>::value);
|
||||
static_assert(!std::is_invocable_r<void, int&>::value);
|
||||
static_assert(!std::is_invocable_r<void, int&&>::value);
|
||||
|
||||
// Result type matches
|
||||
|
||||
template <typename T>
|
||||
T Return();
|
||||
|
||||
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<char, decltype(Return<char>)>::value);
|
||||
static_assert(std::is_invocable_r<int*, decltype(Return<int*>)>::value);
|
||||
static_assert(std::is_invocable_r<int&, decltype(Return<int&>)>::value);
|
||||
static_assert(std::is_invocable_r<int&&, decltype(Return<int&&>)>::value);
|
||||
|
||||
// void result type
|
||||
|
||||
// Any actual return type should be useable with a result type of void.
|
||||
static_assert(std::is_invocable_r<void, decltype(Return<void>)>::value);
|
||||
static_assert(std::is_invocable_r<void, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<void, decltype(Return<int*>)>::value);
|
||||
static_assert(std::is_invocable_r<void, decltype(Return<int&>)>::value);
|
||||
static_assert(std::is_invocable_r<void, decltype(Return<int&&>)>::value);
|
||||
|
||||
// const- and volatile-qualified void should work too.
|
||||
static_assert(std::is_invocable_r<const void, decltype(Return<void>)>::value);
|
||||
static_assert(std::is_invocable_r<const void, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<volatile void, decltype(Return<void>)>::value);
|
||||
static_assert(std::is_invocable_r<volatile void, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<const volatile void, decltype(Return<void>)>::value);
|
||||
static_assert(std::is_invocable_r<const volatile void, decltype(Return<int>)>::value);
|
||||
|
||||
// Conversion of result type
|
||||
|
||||
// It should be possible to use a result type to which the actual return type
|
||||
// can be converted.
|
||||
static_assert(std::is_invocable_r<char, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<const int*, decltype(Return<int*>)>::value);
|
||||
static_assert(std::is_invocable_r<void*, decltype(Return<int*>)>::value);
|
||||
static_assert(std::is_invocable_r<const int&, decltype(Return<int>)>::value);
|
||||
static_assert(std::is_invocable_r<const int&, decltype(Return<int&>)>::value);
|
||||
static_assert(std::is_invocable_r<const int&, decltype(Return<int&&>)>::value);
|
||||
static_assert(std::is_invocable_r<const char&, decltype(Return<int>)>::value);
|
||||
|
||||
// But not a result type where the conversion doesn't work.
|
||||
static_assert(!std::is_invocable_r<int, decltype(Return<void>)>::value);
|
||||
static_assert(!std::is_invocable_r<int, decltype(Return<int*>)>::value);
|
||||
|
||||
// Non-moveable result type
|
||||
|
||||
// Define a type that can't be move-constructed.
|
||||
struct CantMove {
|
||||
CantMove() = default;
|
||||
CantMove(CantMove&&) = delete;
|
||||
};
|
||||
|
||||
static_assert(!std::is_move_constructible_v<CantMove>);
|
||||
static_assert(!std::is_copy_constructible_v<CantMove>);
|
||||
|
||||
// Define functions that return that type.
|
||||
CantMove MakeCantMove() { return {}; }
|
||||
CantMove MakeCantMoveWithArg(int) { return {}; }
|
||||
|
||||
// Assumption check: it should be possible to call one of those functions and
|
||||
// use it to initialize a CantMove object.
|
||||
CantMove cant_move = MakeCantMove();
|
||||
|
||||
// Therefore std::is_invocable_r should agree that they can be invoked to yield
|
||||
// a CantMove.
|
||||
static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMove)>::value);
|
||||
static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg), int>::value);
|
||||
|
||||
// Of course it still shouldn't be possible to call one of the functions and get
|
||||
// back some other type.
|
||||
static_assert(!std::is_invocable_r<int, decltype(MakeCantMove)>::value);
|
||||
|
||||
// And the argument types should still be important.
|
||||
static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMove), int>::value);
|
||||
static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg)>::value);
|
||||
|
||||
// is_invocable_r
|
||||
|
||||
// The struct form should be available too, not just the _v variant.
|
||||
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
|
||||
static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);
|
|
@ -0,0 +1,103 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
|
||||
// is_invocable_r
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
// Non-invocable types
|
||||
|
||||
static_assert(!std::is_invocable_r_v<void, void>);
|
||||
static_assert(!std::is_invocable_r_v<void, int>);
|
||||
static_assert(!std::is_invocable_r_v<void, int*>);
|
||||
static_assert(!std::is_invocable_r_v<void, int&>);
|
||||
static_assert(!std::is_invocable_r_v<void, int&&>);
|
||||
|
||||
// Result type matches
|
||||
|
||||
template <typename T>
|
||||
T Return();
|
||||
|
||||
static_assert(std::is_invocable_r_v<int, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<char, decltype(Return<char>)>);
|
||||
static_assert(std::is_invocable_r_v<int*, decltype(Return<int*>)>);
|
||||
static_assert(std::is_invocable_r_v<int&, decltype(Return<int&>)>);
|
||||
static_assert(std::is_invocable_r_v<int&&, decltype(Return<int&&>)>);
|
||||
|
||||
// void result type
|
||||
|
||||
// Any actual return type should be useable with a result type of void.
|
||||
static_assert(std::is_invocable_r_v<void, decltype(Return<void>)>);
|
||||
static_assert(std::is_invocable_r_v<void, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<void, decltype(Return<int*>)>);
|
||||
static_assert(std::is_invocable_r_v<void, decltype(Return<int&>)>);
|
||||
static_assert(std::is_invocable_r_v<void, decltype(Return<int&&>)>);
|
||||
|
||||
// const- and volatile-qualified void should work too.
|
||||
static_assert(std::is_invocable_r_v<const void, decltype(Return<void>)>);
|
||||
static_assert(std::is_invocable_r_v<const void, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<volatile void, decltype(Return<void>)>);
|
||||
static_assert(std::is_invocable_r_v<volatile void, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<void>)>);
|
||||
static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<int>)>);
|
||||
|
||||
// Conversion of result type
|
||||
|
||||
// It should be possible to use a result type to which the actual return type
|
||||
// can be converted.
|
||||
static_assert(std::is_invocable_r_v<char, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<const int*, decltype(Return<int*>)>);
|
||||
static_assert(std::is_invocable_r_v<void*, decltype(Return<int*>)>);
|
||||
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int>)>);
|
||||
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&>)>);
|
||||
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&&>)>);
|
||||
static_assert(std::is_invocable_r_v<const char&, decltype(Return<int>)>);
|
||||
|
||||
// But not a result type where the conversion doesn't work.
|
||||
static_assert(!std::is_invocable_r_v<int, decltype(Return<void>)>);
|
||||
static_assert(!std::is_invocable_r_v<int, decltype(Return<int*>)>);
|
||||
|
||||
// Non-moveable result type
|
||||
|
||||
// Define a type that can't be move-constructed.
|
||||
struct CantMove {
|
||||
CantMove() = default;
|
||||
CantMove(CantMove&&) = delete;
|
||||
};
|
||||
|
||||
static_assert(!std::is_move_constructible_v<CantMove>);
|
||||
static_assert(!std::is_copy_constructible_v<CantMove>);
|
||||
|
||||
// Define functions that return that type.
|
||||
CantMove MakeCantMove() { return {}; }
|
||||
CantMove MakeCantMoveWithArg(int) { return {}; }
|
||||
|
||||
// Assumption check: it should be possible to call one of those functions and
|
||||
// use it to initialize a CantMove object.
|
||||
CantMove cant_move = MakeCantMove();
|
||||
|
||||
// Therefore std::is_invocable_r should agree that they can be invoked to yield
|
||||
// a CantMove.
|
||||
static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMove)>);
|
||||
static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg), int>);
|
||||
|
||||
// Of course it still shouldn't be possible to call one of the functions and get
|
||||
// back some other type.
|
||||
static_assert(!std::is_invocable_r_v<int, decltype(MakeCantMove)>);
|
||||
|
||||
// And the argument types should still be important.
|
||||
static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMove), int>);
|
||||
static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg)>);
|
||||
|
||||
// is_invocable_r
|
||||
|
||||
// The struct form should be available too, not just the _v variant.
|
||||
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
|
||||
static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);
|
|
@ -185,6 +185,24 @@ int main(int, char**) {
|
|||
static_assert(std::is_nothrow_invocable_r<Implicit, Fn, Tag&>::value, "");
|
||||
static_assert(throws_invocable_r<ThrowsImplicit, Fn, Tag&>(), "");
|
||||
}
|
||||
{
|
||||
// Check that it's fine if the result type is non-moveable.
|
||||
struct CantMove {
|
||||
CantMove() = default;
|
||||
CantMove(CantMove&&) = delete;
|
||||
};
|
||||
|
||||
static_assert(!std::is_move_constructible_v<CantMove>);
|
||||
static_assert(!std::is_copy_constructible_v<CantMove>);
|
||||
|
||||
using Fn = CantMove() noexcept;
|
||||
|
||||
static_assert(std::is_nothrow_invocable_r<CantMove, Fn>::value);
|
||||
static_assert(!std::is_nothrow_invocable_r<CantMove, Fn, int>::value);
|
||||
|
||||
static_assert(std::is_nothrow_invocable_r_v<CantMove, Fn>);
|
||||
static_assert(!std::is_nothrow_invocable_r_v<CantMove, Fn, int>);
|
||||
}
|
||||
{
|
||||
// Check for is_nothrow_invocable_v
|
||||
using Fn = CallObject<true, int>;
|
||||
|
|
Loading…
Reference in New Issue