Skip to content

Commit c3a2488

Browse files
jacobsaphilnik777
authored andcommitted
[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](#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 #55346. Reviewed By: #libc, philnik, EricWF Spies: EricWF, jloser, ldionne, philnik, libcxx-commits Differential Revision: https://reviews.llvm.org/D125300
1 parent c0e06c7 commit c3a2488

File tree

4 files changed

+227
-9
lines changed

4 files changed

+227
-9
lines changed

libcxx/include/type_traits

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2992,16 +2992,10 @@ struct __invokable_r
29922992
// or incomplete array types as required by the standard.
29932993
using _Result = decltype(__try_call<_Fp, _Args...>(0));
29942994

2995-
using type =
2996-
typename conditional<
2995+
using type = typename conditional<
29972996
_IsNotSame<_Result, __nat>::value,
2998-
typename conditional<
2999-
is_void<_Ret>::value,
3000-
true_type,
3001-
is_convertible<_Result, _Ret>
3002-
>::type,
3003-
false_type
3004-
>::type;
2997+
typename conditional< is_void<_Ret>::value, true_type, __is_core_convertible<_Result, _Ret> >::type,
2998+
false_type >::type;
30052999
static const bool value = type::value;
30063000
};
30073001
template <class _Fp, class ..._Args>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14
10+
11+
// is_invocable_r
12+
13+
#include <type_traits>
14+
15+
// Non-invocable types
16+
17+
static_assert(!std::is_invocable_r<void, void>::value);
18+
static_assert(!std::is_invocable_r<void, int>::value);
19+
static_assert(!std::is_invocable_r<void, int*>::value);
20+
static_assert(!std::is_invocable_r<void, int&>::value);
21+
static_assert(!std::is_invocable_r<void, int&&>::value);
22+
23+
// Result type matches
24+
25+
template <typename T>
26+
T Return();
27+
28+
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
29+
static_assert(std::is_invocable_r<char, decltype(Return<char>)>::value);
30+
static_assert(std::is_invocable_r<int*, decltype(Return<int*>)>::value);
31+
static_assert(std::is_invocable_r<int&, decltype(Return<int&>)>::value);
32+
static_assert(std::is_invocable_r<int&&, decltype(Return<int&&>)>::value);
33+
34+
// void result type
35+
36+
// Any actual return type should be useable with a result type of void.
37+
static_assert(std::is_invocable_r<void, decltype(Return<void>)>::value);
38+
static_assert(std::is_invocable_r<void, decltype(Return<int>)>::value);
39+
static_assert(std::is_invocable_r<void, decltype(Return<int*>)>::value);
40+
static_assert(std::is_invocable_r<void, decltype(Return<int&>)>::value);
41+
static_assert(std::is_invocable_r<void, decltype(Return<int&&>)>::value);
42+
43+
// const- and volatile-qualified void should work too.
44+
static_assert(std::is_invocable_r<const void, decltype(Return<void>)>::value);
45+
static_assert(std::is_invocable_r<const void, decltype(Return<int>)>::value);
46+
static_assert(std::is_invocable_r<volatile void, decltype(Return<void>)>::value);
47+
static_assert(std::is_invocable_r<volatile void, decltype(Return<int>)>::value);
48+
static_assert(std::is_invocable_r<const volatile void, decltype(Return<void>)>::value);
49+
static_assert(std::is_invocable_r<const volatile void, decltype(Return<int>)>::value);
50+
51+
// Conversion of result type
52+
53+
// It should be possible to use a result type to which the actual return type
54+
// can be converted.
55+
static_assert(std::is_invocable_r<char, decltype(Return<int>)>::value);
56+
static_assert(std::is_invocable_r<const int*, decltype(Return<int*>)>::value);
57+
static_assert(std::is_invocable_r<void*, decltype(Return<int*>)>::value);
58+
static_assert(std::is_invocable_r<const int&, decltype(Return<int>)>::value);
59+
static_assert(std::is_invocable_r<const int&, decltype(Return<int&>)>::value);
60+
static_assert(std::is_invocable_r<const int&, decltype(Return<int&&>)>::value);
61+
static_assert(std::is_invocable_r<const char&, decltype(Return<int>)>::value);
62+
63+
// But not a result type where the conversion doesn't work.
64+
static_assert(!std::is_invocable_r<int, decltype(Return<void>)>::value);
65+
static_assert(!std::is_invocable_r<int, decltype(Return<int*>)>::value);
66+
67+
// Non-moveable result type
68+
69+
// Define a type that can't be move-constructed.
70+
struct CantMove {
71+
CantMove() = default;
72+
CantMove(CantMove&&) = delete;
73+
};
74+
75+
static_assert(!std::is_move_constructible_v<CantMove>);
76+
static_assert(!std::is_copy_constructible_v<CantMove>);
77+
78+
// Define functions that return that type.
79+
CantMove MakeCantMove() { return {}; }
80+
CantMove MakeCantMoveWithArg(int) { return {}; }
81+
82+
// Assumption check: it should be possible to call one of those functions and
83+
// use it to initialize a CantMove object.
84+
CantMove cant_move = MakeCantMove();
85+
86+
// Therefore std::is_invocable_r should agree that they can be invoked to yield
87+
// a CantMove.
88+
static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMove)>::value);
89+
static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg), int>::value);
90+
91+
// Of course it still shouldn't be possible to call one of the functions and get
92+
// back some other type.
93+
static_assert(!std::is_invocable_r<int, decltype(MakeCantMove)>::value);
94+
95+
// And the argument types should still be important.
96+
static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMove), int>::value);
97+
static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg)>::value);
98+
99+
// is_invocable_r
100+
101+
// The struct form should be available too, not just the _v variant.
102+
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
103+
static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14
10+
11+
// is_invocable_r
12+
13+
#include <type_traits>
14+
15+
// Non-invocable types
16+
17+
static_assert(!std::is_invocable_r_v<void, void>);
18+
static_assert(!std::is_invocable_r_v<void, int>);
19+
static_assert(!std::is_invocable_r_v<void, int*>);
20+
static_assert(!std::is_invocable_r_v<void, int&>);
21+
static_assert(!std::is_invocable_r_v<void, int&&>);
22+
23+
// Result type matches
24+
25+
template <typename T>
26+
T Return();
27+
28+
static_assert(std::is_invocable_r_v<int, decltype(Return<int>)>);
29+
static_assert(std::is_invocable_r_v<char, decltype(Return<char>)>);
30+
static_assert(std::is_invocable_r_v<int*, decltype(Return<int*>)>);
31+
static_assert(std::is_invocable_r_v<int&, decltype(Return<int&>)>);
32+
static_assert(std::is_invocable_r_v<int&&, decltype(Return<int&&>)>);
33+
34+
// void result type
35+
36+
// Any actual return type should be useable with a result type of void.
37+
static_assert(std::is_invocable_r_v<void, decltype(Return<void>)>);
38+
static_assert(std::is_invocable_r_v<void, decltype(Return<int>)>);
39+
static_assert(std::is_invocable_r_v<void, decltype(Return<int*>)>);
40+
static_assert(std::is_invocable_r_v<void, decltype(Return<int&>)>);
41+
static_assert(std::is_invocable_r_v<void, decltype(Return<int&&>)>);
42+
43+
// const- and volatile-qualified void should work too.
44+
static_assert(std::is_invocable_r_v<const void, decltype(Return<void>)>);
45+
static_assert(std::is_invocable_r_v<const void, decltype(Return<int>)>);
46+
static_assert(std::is_invocable_r_v<volatile void, decltype(Return<void>)>);
47+
static_assert(std::is_invocable_r_v<volatile void, decltype(Return<int>)>);
48+
static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<void>)>);
49+
static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<int>)>);
50+
51+
// Conversion of result type
52+
53+
// It should be possible to use a result type to which the actual return type
54+
// can be converted.
55+
static_assert(std::is_invocable_r_v<char, decltype(Return<int>)>);
56+
static_assert(std::is_invocable_r_v<const int*, decltype(Return<int*>)>);
57+
static_assert(std::is_invocable_r_v<void*, decltype(Return<int*>)>);
58+
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int>)>);
59+
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&>)>);
60+
static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&&>)>);
61+
static_assert(std::is_invocable_r_v<const char&, decltype(Return<int>)>);
62+
63+
// But not a result type where the conversion doesn't work.
64+
static_assert(!std::is_invocable_r_v<int, decltype(Return<void>)>);
65+
static_assert(!std::is_invocable_r_v<int, decltype(Return<int*>)>);
66+
67+
// Non-moveable result type
68+
69+
// Define a type that can't be move-constructed.
70+
struct CantMove {
71+
CantMove() = default;
72+
CantMove(CantMove&&) = delete;
73+
};
74+
75+
static_assert(!std::is_move_constructible_v<CantMove>);
76+
static_assert(!std::is_copy_constructible_v<CantMove>);
77+
78+
// Define functions that return that type.
79+
CantMove MakeCantMove() { return {}; }
80+
CantMove MakeCantMoveWithArg(int) { return {}; }
81+
82+
// Assumption check: it should be possible to call one of those functions and
83+
// use it to initialize a CantMove object.
84+
CantMove cant_move = MakeCantMove();
85+
86+
// Therefore std::is_invocable_r should agree that they can be invoked to yield
87+
// a CantMove.
88+
static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMove)>);
89+
static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg), int>);
90+
91+
// Of course it still shouldn't be possible to call one of the functions and get
92+
// back some other type.
93+
static_assert(!std::is_invocable_r_v<int, decltype(MakeCantMove)>);
94+
95+
// And the argument types should still be important.
96+
static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMove), int>);
97+
static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg)>);
98+
99+
// is_invocable_r
100+
101+
// The struct form should be available too, not just the _v variant.
102+
static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
103+
static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);

libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ int main(int, char**) {
185185
static_assert(std::is_nothrow_invocable_r<Implicit, Fn, Tag&>::value, "");
186186
static_assert(throws_invocable_r<ThrowsImplicit, Fn, Tag&>(), "");
187187
}
188+
{
189+
// Check that it's fine if the result type is non-moveable.
190+
struct CantMove {
191+
CantMove() = default;
192+
CantMove(CantMove&&) = delete;
193+
};
194+
195+
static_assert(!std::is_move_constructible_v<CantMove>);
196+
static_assert(!std::is_copy_constructible_v<CantMove>);
197+
198+
using Fn = CantMove() noexcept;
199+
200+
static_assert(std::is_nothrow_invocable_r<CantMove, Fn>::value);
201+
static_assert(!std::is_nothrow_invocable_r<CantMove, Fn, int>::value);
202+
203+
static_assert(std::is_nothrow_invocable_r_v<CantMove, Fn>);
204+
static_assert(!std::is_nothrow_invocable_r_v<CantMove, Fn, int>);
205+
}
188206
{
189207
// Check for is_nothrow_invocable_v
190208
using Fn = CallObject<true, int>;

0 commit comments

Comments
 (0)