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 | // <tuple> |
12 | |
13 | // template <class F, class T> constexpr decltype(auto) apply(F &&, T &&) noexcept(see below) // noexcept since C++23 |
14 | |
15 | // Test with different ref/ptr/cv qualified argument types. |
16 | |
17 | #include <tuple> |
18 | #include <array> |
19 | #include <utility> |
20 | #include <cassert> |
21 | |
22 | #include "test_macros.h" |
23 | #include "type_id.h" |
24 | |
25 | constexpr int constexpr_sum_fn() { return 0; } |
26 | |
27 | template <class ...Ints> |
28 | constexpr int constexpr_sum_fn(int x1, Ints... rest) { return x1 + constexpr_sum_fn(rest...); } |
29 | |
30 | struct ConstexprSumT { |
31 | constexpr ConstexprSumT() = default; |
32 | template <class ...Ints> |
33 | constexpr int operator()(Ints... values) const { |
34 | return constexpr_sum_fn(values...); |
35 | } |
36 | }; |
37 | |
38 | |
39 | void test_constexpr_evaluation() |
40 | { |
41 | constexpr ConstexprSumT sum_obj{}; |
42 | { |
43 | using Tup = std::tuple<>; |
44 | using Fn = int(&)(); |
45 | constexpr Tup t; |
46 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 0, "" ); |
47 | static_assert(std::apply(f: sum_obj, t: t) == 0, "" ); |
48 | } |
49 | { |
50 | using Tup = std::tuple<int>; |
51 | using Fn = int(&)(int); |
52 | constexpr Tup t(42); |
53 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 42, "" ); |
54 | static_assert(std::apply(f: sum_obj, t: t) == 42, "" ); |
55 | } |
56 | { |
57 | using Tup = std::tuple<int, long>; |
58 | using Fn = int(&)(int, int); |
59 | constexpr Tup t(42, 101); |
60 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 143, "" ); |
61 | static_assert(std::apply(f: sum_obj, t: t) == 143, "" ); |
62 | } |
63 | { |
64 | using Tup = std::pair<int, long>; |
65 | using Fn = int(&)(int, int); |
66 | constexpr Tup t(42, 101); |
67 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 143, "" ); |
68 | static_assert(std::apply(f: sum_obj, t: t) == 143, "" ); |
69 | } |
70 | { |
71 | using Tup = std::tuple<int, long, int>; |
72 | using Fn = int(&)(int, int, int); |
73 | constexpr Tup t(42, 101, -1); |
74 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 142, "" ); |
75 | static_assert(std::apply(f: sum_obj, t: t) == 142, "" ); |
76 | } |
77 | { |
78 | using Tup = std::array<int, 3>; |
79 | using Fn = int(&)(int, int, int); |
80 | constexpr Tup t = {42, 101, -1}; |
81 | static_assert(std::apply(f&: static_cast<Fn>(constexpr_sum_fn), t: t) == 142, "" ); |
82 | static_assert(std::apply(f: sum_obj, t: t) == 142, "" ); |
83 | } |
84 | } |
85 | |
86 | |
87 | enum CallQuals { |
88 | CQ_None, |
89 | CQ_LValue, |
90 | CQ_ConstLValue, |
91 | CQ_RValue, |
92 | CQ_ConstRValue |
93 | }; |
94 | |
95 | template <class Tuple> |
96 | struct CallInfo { |
97 | CallQuals quals; |
98 | TypeID const* arg_types; |
99 | Tuple args; |
100 | |
101 | template <class ...Args> |
102 | CallInfo(CallQuals q, Args&&... xargs) |
103 | : quals(q), arg_types(&makeArgumentID<Args&&...>()), args(std::forward<Args>(xargs)...) |
104 | {} |
105 | }; |
106 | |
107 | template <class ...Args> |
108 | inline CallInfo<decltype(std::forward_as_tuple(std::declval<Args>()...))> |
109 | makeCallInfo(CallQuals quals, Args&&... args) { |
110 | return {quals, std::forward<Args>(args)...}; |
111 | } |
112 | |
113 | struct TrackedCallable { |
114 | |
115 | TrackedCallable() = default; |
116 | |
117 | template <class ...Args> auto operator()(Args&&... xargs) & |
118 | { return makeCallInfo(CQ_LValue, std::forward<Args>(xargs)...); } |
119 | |
120 | template <class ...Args> auto operator()(Args&&... xargs) const& |
121 | { return makeCallInfo(CQ_ConstLValue, std::forward<Args>(xargs)...); } |
122 | |
123 | template <class ...Args> auto operator()(Args&&... xargs) && |
124 | { return makeCallInfo(CQ_RValue, std::forward<Args>(xargs)...); } |
125 | |
126 | template <class ...Args> auto operator()(Args&&... xargs) const&& |
127 | { return makeCallInfo(CQ_ConstRValue, std::forward<Args>(xargs)...); } |
128 | }; |
129 | |
130 | template <class ...ExpectArgs, class Tuple> |
131 | void check_apply_quals_and_types(Tuple&& t) { |
132 | TypeID const* const expect_args = &makeArgumentID<ExpectArgs...>(); |
133 | TrackedCallable obj; |
134 | TrackedCallable const& cobj = obj; |
135 | { |
136 | auto ret = std::apply(obj, std::forward<Tuple>(t)); |
137 | assert(ret.quals == CQ_LValue); |
138 | assert(ret.arg_types == expect_args); |
139 | assert(ret.args == t); |
140 | } |
141 | { |
142 | auto ret = std::apply(cobj, std::forward<Tuple>(t)); |
143 | assert(ret.quals == CQ_ConstLValue); |
144 | assert(ret.arg_types == expect_args); |
145 | assert(ret.args == t); |
146 | } |
147 | { |
148 | auto ret = std::apply(std::move(obj), std::forward<Tuple>(t)); |
149 | assert(ret.quals == CQ_RValue); |
150 | assert(ret.arg_types == expect_args); |
151 | assert(ret.args == t); |
152 | } |
153 | { |
154 | auto ret = std::apply(std::move(cobj), std::forward<Tuple>(t)); |
155 | assert(ret.quals == CQ_ConstRValue); |
156 | assert(ret.arg_types == expect_args); |
157 | assert(ret.args == t); |
158 | } |
159 | } |
160 | |
161 | void test_call_quals_and_arg_types() |
162 | { |
163 | using Tup = std::tuple<int, int const&, unsigned&&>; |
164 | const int x = 42; |
165 | unsigned y = 101; |
166 | Tup t(-1, x, std::move(y)); |
167 | Tup const& ct = t; |
168 | check_apply_quals_and_types<int&, int const&, unsigned&>(t); |
169 | check_apply_quals_and_types<int const&, int const&, unsigned&>(t: ct); |
170 | check_apply_quals_and_types<int&&, int const&, unsigned&&>(t: std::move(t)); |
171 | check_apply_quals_and_types<int const&&, int const&, unsigned&&>(t: std::move(ct)); |
172 | } |
173 | |
174 | |
175 | struct NothrowMoveable { |
176 | NothrowMoveable() noexcept = default; |
177 | NothrowMoveable(NothrowMoveable const&) noexcept(false) {} |
178 | NothrowMoveable(NothrowMoveable&&) noexcept {} |
179 | }; |
180 | |
181 | template <bool IsNoexcept> |
182 | struct TestNoexceptCallable { |
183 | template <class ...Args> |
184 | NothrowMoveable operator()(Args...) const noexcept(IsNoexcept) { return {}; } |
185 | }; |
186 | |
187 | void test_noexcept() |
188 | { |
189 | TestNoexceptCallable<true> nec; |
190 | TestNoexceptCallable<false> tc; |
191 | { |
192 | // test that the functions noexcept-ness is propagated |
193 | using Tup = std::tuple<int, const char*, long>; |
194 | Tup t; |
195 | #if TEST_STD_VER >= 23 |
196 | ASSERT_NOEXCEPT(std::apply(nec, t)); |
197 | #else |
198 | LIBCPP_ASSERT_NOEXCEPT(std::apply(f&: nec, t&: t)); |
199 | #endif |
200 | ASSERT_NOT_NOEXCEPT(std::apply(f&: tc, t&: t)); |
201 | } |
202 | { |
203 | // test that the noexcept-ness of the argument conversions is checked. |
204 | using Tup = std::tuple<NothrowMoveable, int>; |
205 | Tup t; |
206 | ASSERT_NOT_NOEXCEPT(std::apply(f&: nec, t&: t)); |
207 | #if TEST_STD_VER >= 23 |
208 | ASSERT_NOEXCEPT(std::apply(nec, std::move(t))); |
209 | #else |
210 | LIBCPP_ASSERT_NOEXCEPT(std::apply(f&: nec, t: std::move(t))); |
211 | #endif |
212 | } |
213 | } |
214 | |
215 | namespace ReturnTypeTest { |
216 | static int my_int = 42; |
217 | |
218 | template <int N> struct index {}; |
219 | |
220 | void f(index<0>) {} |
221 | |
222 | int f(index<1>) { return 0; } |
223 | |
224 | int & f(index<2>) { return static_cast<int &>(my_int); } |
225 | int const & f(index<3>) { return static_cast<int const &>(my_int); } |
226 | int volatile & f(index<4>) { return static_cast<int volatile &>(my_int); } |
227 | int const volatile & f(index<5>) { return static_cast<int const volatile &>(my_int); } |
228 | |
229 | int && f(index<6>) { return static_cast<int &&>(my_int); } |
230 | int const && f(index<7>) { return static_cast<int const &&>(my_int); } |
231 | int volatile && f(index<8>) { return static_cast<int volatile &&>(my_int); } |
232 | int const volatile && f(index<9>) { return static_cast<int const volatile &&>(my_int); } |
233 | |
234 | int * f(index<10>) { return static_cast<int *>(&my_int); } |
235 | int const * f(index<11>) { return static_cast<int const *>(&my_int); } |
236 | int volatile * f(index<12>) { return static_cast<int volatile *>(&my_int); } |
237 | int const volatile * f(index<13>) { return static_cast<int const volatile *>(&my_int); } |
238 | |
239 | template <int Func, class Expect> |
240 | void test() |
241 | { |
242 | using RawInvokeResult = decltype(f(index<Func>{})); |
243 | static_assert(std::is_same<RawInvokeResult, Expect>::value, "" ); |
244 | using FnType = RawInvokeResult (*) (index<Func>); |
245 | FnType fn = f; |
246 | std::tuple<index<Func>> t; ((void)t); |
247 | using InvokeResult = decltype(std::apply(fn, t)); |
248 | static_assert(std::is_same<InvokeResult, Expect>::value, "" ); |
249 | } |
250 | } // end namespace ReturnTypeTest |
251 | |
252 | void test_return_type() |
253 | { |
254 | using ReturnTypeTest::test; |
255 | test<0, void>(); |
256 | test<1, int>(); |
257 | test<2, int &>(); |
258 | test<3, int const &>(); |
259 | test<4, int volatile &>(); |
260 | test<5, int const volatile &>(); |
261 | test<6, int &&>(); |
262 | test<7, int const &&>(); |
263 | test<8, int volatile &&>(); |
264 | test<9, int const volatile &&>(); |
265 | test<10, int *>(); |
266 | test<11, int const *>(); |
267 | test<12, int volatile *>(); |
268 | test<13, int const volatile *>(); |
269 | } |
270 | |
271 | int main(int, char**) { |
272 | test_constexpr_evaluation(); |
273 | test_call_quals_and_arg_types(); |
274 | test_return_type(); |
275 | test_noexcept(); |
276 | |
277 | return 0; |
278 | } |
279 | |