1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QFUTURE_H |
5 | #error Do not include qfuture_impl.h directly |
6 | #endif |
7 | |
8 | #if 0 |
9 | #pragma qt_sync_skip_header_check |
10 | #pragma qt_sync_stop_processing |
11 | #endif |
12 | |
13 | #include <QtCore/qglobal.h> |
14 | #include <QtCore/qfutureinterface.h> |
15 | #include <QtCore/qthreadpool.h> |
16 | #include <QtCore/qexception.h> |
17 | #include <QtCore/qpointer.h> |
18 | #include <QtCore/qpromise.h> |
19 | |
20 | #include <memory> |
21 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | // |
25 | // forward declarations |
26 | // |
27 | template<class T> |
28 | class QFuture; |
29 | template<class T> |
30 | class QFutureInterface; |
31 | template<class T> |
32 | class QPromise; |
33 | |
34 | namespace QtFuture { |
35 | |
36 | enum class Launch { Sync, Async, Inherit }; |
37 | |
38 | template<class T> |
39 | struct WhenAnyResult |
40 | { |
41 | qsizetype index = -1; |
42 | QFuture<T> future; |
43 | }; |
44 | |
45 | // Deduction guide |
46 | template<class T> |
47 | WhenAnyResult(qsizetype, const QFuture<T> &) -> WhenAnyResult<T>; |
48 | } |
49 | |
50 | namespace QtPrivate { |
51 | |
52 | template<class T> |
53 | using EnableForVoid = std::enable_if_t<std::is_same_v<T, void>>; |
54 | |
55 | template<class T> |
56 | using EnableForNonVoid = std::enable_if_t<!std::is_same_v<T, void>>; |
57 | |
58 | template<typename F, typename Arg, typename Enable = void> |
59 | struct ResultTypeHelper |
60 | { |
61 | }; |
62 | |
63 | // The callable takes an argument of type Arg |
64 | template<typename F, typename Arg> |
65 | struct ResultTypeHelper< |
66 | F, Arg, typename std::enable_if_t<!std::is_invocable_v<std::decay_t<F>, QFuture<Arg>>>> |
67 | { |
68 | using ResultType = std::invoke_result_t<std::decay_t<F>, std::decay_t<Arg>>; |
69 | }; |
70 | |
71 | // The callable takes an argument of type QFuture<Arg> |
72 | template<class F, class Arg> |
73 | struct ResultTypeHelper< |
74 | F, Arg, typename std::enable_if_t<std::is_invocable_v<std::decay_t<F>, QFuture<Arg>>>> |
75 | { |
76 | using ResultType = std::invoke_result_t<std::decay_t<F>, QFuture<Arg>>; |
77 | }; |
78 | |
79 | // The callable takes an argument of type QFuture<void> |
80 | template<class F> |
81 | struct ResultTypeHelper< |
82 | F, void, typename std::enable_if_t<std::is_invocable_v<std::decay_t<F>, QFuture<void>>>> |
83 | { |
84 | using ResultType = std::invoke_result_t<std::decay_t<F>, QFuture<void>>; |
85 | }; |
86 | |
87 | // The callable doesn't take argument |
88 | template<class F> |
89 | struct ResultTypeHelper< |
90 | F, void, typename std::enable_if_t<!std::is_invocable_v<std::decay_t<F>, QFuture<void>>>> |
91 | { |
92 | using ResultType = std::invoke_result_t<std::decay_t<F>>; |
93 | }; |
94 | |
95 | // Helpers to remove QPrivateSignal argument from the list of arguments |
96 | |
97 | template<class T, class Enable = void> |
98 | inline constexpr bool IsPrivateSignalArg = false; |
99 | |
100 | template<class T> |
101 | inline constexpr bool IsPrivateSignalArg<T, typename std::enable_if_t< |
102 | // finds injected-class-name, the 'class' avoids falling into the rules of [class.qual]/2: |
103 | std::is_class_v<class T::QPrivateSignal> |
104 | >> = true; |
105 | |
106 | template<class Tuple, std::size_t... I> |
107 | auto cutTuple(Tuple &&t, std::index_sequence<I...>) |
108 | { |
109 | return std::make_tuple(std::get<I>(t)...); |
110 | } |
111 | |
112 | template<class Arg, class... Args> |
113 | auto createTuple(Arg &&arg, Args &&... args) |
114 | { |
115 | using TupleType = std::tuple<std::decay_t<Arg>, std::decay_t<Args>...>; |
116 | constexpr auto Size = sizeof...(Args); // One less than the size of all arguments |
117 | if constexpr (QtPrivate::IsPrivateSignalArg<std::tuple_element_t<Size, TupleType>>) { |
118 | if constexpr (Size == 1) { |
119 | return std::forward<Arg>(arg); |
120 | } else { |
121 | return cutTuple(std::make_tuple(std::forward<Arg>(arg), std::forward<Args>(args)...), |
122 | std::make_index_sequence<Size>()); |
123 | } |
124 | } else { |
125 | return std::make_tuple(std::forward<Arg>(arg), std::forward<Args>(args)...); |
126 | } |
127 | } |
128 | |
129 | // Helpers to resolve argument types of callables. |
130 | |
131 | template<class Arg, class... Args> |
132 | using FilterLastPrivateSignalArg = |
133 | std::conditional_t<(sizeof...(Args) > 0), |
134 | std::invoke_result_t<decltype(createTuple<Arg, Args...>), Arg, Args...>, |
135 | std::conditional_t<IsPrivateSignalArg<Arg>, void, Arg>>; |
136 | |
137 | template<typename...> |
138 | struct ArgsType; |
139 | |
140 | template<typename Arg, typename... Args> |
141 | struct ArgsType<Arg, Args...> |
142 | { |
143 | using First = Arg; |
144 | using PromiseType = void; |
145 | using IsPromise = std::false_type; |
146 | static const bool = (sizeof...(Args) > 0); |
147 | using AllArgs = FilterLastPrivateSignalArg<std::decay_t<Arg>, std::decay_t<Args>...>; |
148 | |
149 | template<class Class, class Callable> |
150 | static const bool CanInvokeWithArgs = std::is_invocable_v<Callable, Class, Arg, Args...>; |
151 | }; |
152 | |
153 | template<typename Arg, typename... Args> |
154 | struct ArgsType<QPromise<Arg> &, Args...> |
155 | { |
156 | using First = QPromise<Arg> &; |
157 | using PromiseType = Arg; |
158 | using IsPromise = std::true_type; |
159 | static const bool = (sizeof...(Args) > 0); |
160 | using AllArgs = FilterLastPrivateSignalArg<QPromise<Arg>, std::decay_t<Args>...>; |
161 | |
162 | template<class Class, class Callable> |
163 | static const bool CanInvokeWithArgs = std::is_invocable_v<Callable, Class, QPromise<Arg> &, Args...>; |
164 | }; |
165 | |
166 | template<> |
167 | struct ArgsType<> |
168 | { |
169 | using First = void; |
170 | using PromiseType = void; |
171 | using IsPromise = std::false_type; |
172 | static const bool = false; |
173 | using AllArgs = void; |
174 | |
175 | template<class Class, class Callable> |
176 | static const bool CanInvokeWithArgs = std::is_invocable_v<Callable, Class>; |
177 | }; |
178 | |
179 | template<typename F> |
180 | struct ArgResolver : ArgResolver<decltype(&std::decay_t<F>::operator())> |
181 | { |
182 | }; |
183 | |
184 | template<typename F> |
185 | struct ArgResolver<std::reference_wrapper<F>> : ArgResolver<decltype(&std::decay_t<F>::operator())> |
186 | { |
187 | }; |
188 | |
189 | template<typename R, typename... Args> |
190 | struct ArgResolver<R(Args...)> : public ArgsType<Args...> |
191 | { |
192 | }; |
193 | |
194 | template<typename R, typename... Args> |
195 | struct ArgResolver<R (*)(Args...)> : public ArgsType<Args...> |
196 | { |
197 | }; |
198 | |
199 | template<typename R, typename... Args> |
200 | struct ArgResolver<R (*&)(Args...)> : public ArgsType<Args...> |
201 | { |
202 | }; |
203 | |
204 | template<typename R, typename... Args> |
205 | struct ArgResolver<R (* const)(Args...)> : public ArgsType<Args...> |
206 | { |
207 | }; |
208 | |
209 | template<typename R, typename... Args> |
210 | struct ArgResolver<R (&)(Args...)> : public ArgsType<Args...> |
211 | { |
212 | }; |
213 | |
214 | template<typename Class, typename R, typename... Args> |
215 | struct ArgResolver<R (Class::*)(Args...)> : public ArgsType<Args...> |
216 | { |
217 | }; |
218 | |
219 | template<typename Class, typename R, typename... Args> |
220 | struct ArgResolver<R (Class::*)(Args...) noexcept> : public ArgsType<Args...> |
221 | { |
222 | }; |
223 | |
224 | template<typename Class, typename R, typename... Args> |
225 | struct ArgResolver<R (Class::*)(Args...) const> : public ArgsType<Args...> |
226 | { |
227 | }; |
228 | |
229 | template<typename Class, typename R, typename... Args> |
230 | struct ArgResolver<R (Class::*)(Args...) const noexcept> : public ArgsType<Args...> |
231 | { |
232 | }; |
233 | |
234 | template<typename Class, typename R, typename... Args> |
235 | struct ArgResolver<R (Class::* const)(Args...) const> : public ArgsType<Args...> |
236 | { |
237 | }; |
238 | |
239 | template<typename Class, typename R, typename... Args> |
240 | struct ArgResolver<R (Class::* const)(Args...) const noexcept> : public ArgsType<Args...> |
241 | { |
242 | }; |
243 | |
244 | template<class Class, class Callable> |
245 | using EnableIfInvocable = std::enable_if_t< |
246 | QtPrivate::ArgResolver<Callable>::template CanInvokeWithArgs<Class, Callable>>; |
247 | |
248 | template<class T> |
249 | inline constexpr bool isQFutureV = false; |
250 | |
251 | template<class T> |
252 | inline constexpr bool isQFutureV<QFuture<T>> = true; |
253 | |
254 | template<class T> |
255 | using isQFuture = std::bool_constant<isQFutureV<T>>; |
256 | |
257 | template<class T> |
258 | struct Future |
259 | { |
260 | }; |
261 | |
262 | template<class T> |
263 | struct Future<QFuture<T>> |
264 | { |
265 | using type = T; |
266 | }; |
267 | |
268 | template<class... Args> |
269 | using NotEmpty = std::bool_constant<(sizeof...(Args) > 0)>; |
270 | |
271 | template<class Sequence> |
272 | using IsRandomAccessible = |
273 | std::is_convertible<typename std::iterator_traits<std::decay_t<decltype( |
274 | std::begin(std::declval<Sequence>()))>>::iterator_category, |
275 | std::random_access_iterator_tag>; |
276 | |
277 | template<class Sequence> |
278 | using HasInputIterator = |
279 | std::is_convertible<typename std::iterator_traits<std::decay_t<decltype( |
280 | std::begin(std::declval<Sequence>()))>>::iterator_category, |
281 | std::input_iterator_tag>; |
282 | |
283 | template<class Iterator> |
284 | using IsForwardIterable = |
285 | std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category, |
286 | std::forward_iterator_tag>; |
287 | |
288 | template<typename Function, typename ResultType, typename ParentResultType> |
289 | class Continuation |
290 | { |
291 | public: |
292 | template<typename F = Function> |
293 | Continuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p) |
294 | : promise(std::move(p)), parentFuture(f), function(std::forward<F>(func)) |
295 | { |
296 | } |
297 | virtual ~Continuation() = default; |
298 | |
299 | bool execute(); |
300 | |
301 | template<typename F = Function> |
302 | static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &fi, |
303 | QtFuture::Launch policy); |
304 | |
305 | template<typename F = Function> |
306 | static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &fi, |
307 | QThreadPool *pool); |
308 | |
309 | template<typename F = Function> |
310 | static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &fi, |
311 | QObject *context); |
312 | |
313 | private: |
314 | void fulfillPromiseWithResult(); |
315 | void fulfillVoidPromise(); |
316 | void fulfillPromiseWithVoidResult(); |
317 | |
318 | template<class... Args> |
319 | void fulfillPromise(Args &&... args); |
320 | |
321 | protected: |
322 | virtual void runImpl() = 0; |
323 | |
324 | void runFunction(); |
325 | |
326 | protected: |
327 | QPromise<ResultType> promise; |
328 | QFuture<ParentResultType> parentFuture; |
329 | Function function; |
330 | }; |
331 | |
332 | template<typename Function, typename ResultType, typename ParentResultType> |
333 | class SyncContinuation final : public Continuation<Function, ResultType, ParentResultType> |
334 | { |
335 | public: |
336 | template<typename F = Function> |
337 | SyncContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p) |
338 | : Continuation<Function, ResultType, ParentResultType>(std::forward<F>(func), f, |
339 | std::move(p)) |
340 | { |
341 | } |
342 | |
343 | ~SyncContinuation() override = default; |
344 | |
345 | private: |
346 | void runImpl() override { this->runFunction(); } |
347 | }; |
348 | |
349 | template<typename Function, typename ResultType, typename ParentResultType> |
350 | class AsyncContinuation final : public QRunnable, |
351 | public Continuation<Function, ResultType, ParentResultType> |
352 | { |
353 | public: |
354 | template<typename F = Function> |
355 | AsyncContinuation(F &&func, const QFuture<ParentResultType> &f, QPromise<ResultType> &&p, |
356 | QThreadPool *pool = nullptr) |
357 | : Continuation<Function, ResultType, ParentResultType>(std::forward<F>(func), f, |
358 | std::move(p)), |
359 | threadPool(pool) |
360 | { |
361 | } |
362 | |
363 | ~AsyncContinuation() override = default; |
364 | |
365 | private: |
366 | void runImpl() override // from Continuation |
367 | { |
368 | QThreadPool *pool = threadPool ? threadPool : QThreadPool::globalInstance(); |
369 | pool->start(this); |
370 | } |
371 | |
372 | void run() override // from QRunnable |
373 | { |
374 | this->runFunction(); |
375 | } |
376 | |
377 | private: |
378 | QThreadPool *threadPool; |
379 | }; |
380 | |
381 | #ifndef QT_NO_EXCEPTIONS |
382 | |
383 | template<class Function, class ResultType> |
384 | class FailureHandler |
385 | { |
386 | public: |
387 | template<typename F = Function> |
388 | static void create(F &&function, QFuture<ResultType> *future, |
389 | const QFutureInterface<ResultType> &fi); |
390 | |
391 | template<typename F = Function> |
392 | static void create(F &&function, QFuture<ResultType> *future, QFutureInterface<ResultType> &fi, |
393 | QObject *context); |
394 | |
395 | template<typename F = Function> |
396 | FailureHandler(F &&func, const QFuture<ResultType> &f, QPromise<ResultType> &&p) |
397 | : promise(std::move(p)), parentFuture(f), handler(std::forward<F>(func)) |
398 | { |
399 | } |
400 | |
401 | public: |
402 | void run(); |
403 | |
404 | private: |
405 | template<class ArgType> |
406 | void handleException(); |
407 | void handleAllExceptions(); |
408 | |
409 | private: |
410 | QPromise<ResultType> promise; |
411 | QFuture<ResultType> parentFuture; |
412 | Function handler; |
413 | }; |
414 | |
415 | #endif |
416 | |
417 | template<typename Function, typename ResultType, typename ParentResultType> |
418 | void Continuation<Function, ResultType, ParentResultType>::runFunction() |
419 | { |
420 | promise.start(); |
421 | |
422 | Q_ASSERT(parentFuture.isFinished()); |
423 | |
424 | #ifndef QT_NO_EXCEPTIONS |
425 | try { |
426 | #endif |
427 | if constexpr (!std::is_void_v<ResultType>) { |
428 | if constexpr (std::is_void_v<ParentResultType>) { |
429 | fulfillPromiseWithVoidResult(); |
430 | } else if constexpr (std::is_invocable_v<Function, ParentResultType>) { |
431 | fulfillPromiseWithResult(); |
432 | } else { |
433 | // This assert normally should never fail, this is to make sure |
434 | // that nothing unexpected happened. |
435 | static_assert(std::is_invocable_v<Function, QFuture<ParentResultType>>, |
436 | "The continuation is not invocable with the provided arguments" ); |
437 | fulfillPromise(parentFuture); |
438 | } |
439 | } else { |
440 | if constexpr (std::is_void_v<ParentResultType>) { |
441 | if constexpr (std::is_invocable_v<Function, QFuture<void>>) |
442 | function(parentFuture); |
443 | else |
444 | function(); |
445 | } else if constexpr (std::is_invocable_v<Function, ParentResultType>) { |
446 | fulfillVoidPromise(); |
447 | } else { |
448 | // This assert normally should never fail, this is to make sure |
449 | // that nothing unexpected happened. |
450 | static_assert(std::is_invocable_v<Function, QFuture<ParentResultType>>, |
451 | "The continuation is not invocable with the provided arguments" ); |
452 | function(parentFuture); |
453 | } |
454 | } |
455 | #ifndef QT_NO_EXCEPTIONS |
456 | } catch (...) { |
457 | promise.setException(std::current_exception()); |
458 | } |
459 | #endif |
460 | promise.finish(); |
461 | } |
462 | |
463 | template<typename Function, typename ResultType, typename ParentResultType> |
464 | bool Continuation<Function, ResultType, ParentResultType>::execute() |
465 | { |
466 | Q_ASSERT(parentFuture.isFinished()); |
467 | |
468 | if (parentFuture.d.isChainCanceled()) { |
469 | #ifndef QT_NO_EXCEPTIONS |
470 | if (parentFuture.d.hasException()) { |
471 | // If the continuation doesn't take a QFuture argument, propagate the exception |
472 | // to the caller, by reporting it. If the continuation takes a QFuture argument, |
473 | // the user may want to catch the exception inside the continuation, to not |
474 | // interrupt the continuation chain, so don't report anything yet. |
475 | if constexpr (!std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) { |
476 | promise.start(); |
477 | promise.setException(parentFuture.d.exceptionStore().exception()); |
478 | promise.finish(); |
479 | return false; |
480 | } |
481 | } else |
482 | #endif |
483 | { |
484 | promise.start(); |
485 | promise.future().cancel(); |
486 | promise.finish(); |
487 | return false; |
488 | } |
489 | } |
490 | |
491 | runImpl(); |
492 | return true; |
493 | } |
494 | |
495 | // Workaround for keeping move-only lambdas inside std::function |
496 | template<class Function> |
497 | struct ContinuationWrapper |
498 | { |
499 | ContinuationWrapper(Function &&f) : function(std::move(f)) { } |
500 | ContinuationWrapper(const ContinuationWrapper &other) |
501 | : function(std::move(const_cast<ContinuationWrapper &>(other).function)) |
502 | { |
503 | Q_ASSERT_X(false, "QFuture" , "Continuation shouldn't be copied" ); |
504 | } |
505 | ContinuationWrapper(ContinuationWrapper &&other) = default; |
506 | ContinuationWrapper &operator=(ContinuationWrapper &&) = default; |
507 | |
508 | void operator()(const QFutureInterfaceBase &parentData) { function(parentData); } |
509 | |
510 | private: |
511 | Function function; |
512 | }; |
513 | |
514 | template<typename Function, typename ResultType, typename ParentResultType> |
515 | template<typename F> |
516 | void Continuation<Function, ResultType, ParentResultType>::create(F &&func, |
517 | QFuture<ParentResultType> *f, |
518 | QFutureInterface<ResultType> &fi, |
519 | QtFuture::Launch policy) |
520 | { |
521 | Q_ASSERT(f); |
522 | |
523 | QThreadPool *pool = nullptr; |
524 | |
525 | bool launchAsync = (policy == QtFuture::Launch::Async); |
526 | if (policy == QtFuture::Launch::Inherit) { |
527 | launchAsync = f->d.launchAsync(); |
528 | |
529 | // If the parent future was using a custom thread pool, inherit it as well. |
530 | if (launchAsync && f->d.threadPool()) { |
531 | pool = f->d.threadPool(); |
532 | fi.setThreadPool(pool); |
533 | } |
534 | } |
535 | |
536 | fi.setLaunchAsync(launchAsync); |
537 | |
538 | auto continuation = [func = std::forward<F>(func), fi, promise = QPromise(fi), pool, |
539 | launchAsync](const QFutureInterfaceBase &parentData) mutable { |
540 | const auto parent = QFutureInterface<ParentResultType>(parentData).future(); |
541 | Continuation<Function, ResultType, ParentResultType> *continuationJob = nullptr; |
542 | if (launchAsync) { |
543 | auto asyncJob = new AsyncContinuation<Function, ResultType, ParentResultType>( |
544 | std::forward<Function>(func), parent, std::move(promise), pool); |
545 | fi.setRunnable(asyncJob); |
546 | continuationJob = asyncJob; |
547 | } else { |
548 | continuationJob = new SyncContinuation<Function, ResultType, ParentResultType>( |
549 | std::forward<Function>(func), parent, std::move(promise)); |
550 | } |
551 | |
552 | bool isLaunched = continuationJob->execute(); |
553 | // If continuation is successfully launched, AsyncContinuation will be deleted |
554 | // by the QThreadPool which has started it. Synchronous continuation will be |
555 | // executed immediately, so it's safe to always delete it here. |
556 | if (!(launchAsync && isLaunched)) { |
557 | delete continuationJob; |
558 | continuationJob = nullptr; |
559 | } |
560 | }; |
561 | f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); |
562 | } |
563 | |
564 | template<typename Function, typename ResultType, typename ParentResultType> |
565 | template<typename F> |
566 | void Continuation<Function, ResultType, ParentResultType>::create(F &&func, |
567 | QFuture<ParentResultType> *f, |
568 | QFutureInterface<ResultType> &fi, |
569 | QThreadPool *pool) |
570 | { |
571 | Q_ASSERT(f); |
572 | |
573 | fi.setLaunchAsync(true); |
574 | fi.setThreadPool(pool); |
575 | |
576 | auto continuation = [func = std::forward<F>(func), promise = QPromise(fi), |
577 | pool](const QFutureInterfaceBase &parentData) mutable { |
578 | const auto parent = QFutureInterface<ParentResultType>(parentData).future(); |
579 | auto continuationJob = new AsyncContinuation<Function, ResultType, ParentResultType>( |
580 | std::forward<Function>(func), parent, std::move(promise), pool); |
581 | bool isLaunched = continuationJob->execute(); |
582 | // If continuation is successfully launched, AsyncContinuation will be deleted |
583 | // by the QThreadPool which has started it. |
584 | if (!isLaunched) { |
585 | delete continuationJob; |
586 | continuationJob = nullptr; |
587 | } |
588 | }; |
589 | f->d.setContinuation(ContinuationWrapper(std::move(continuation)), fi.d); |
590 | } |
591 | |
592 | // defined in qfutureinterface.cpp: |
593 | Q_CORE_EXPORT void watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj, |
594 | QFutureInterfaceBase &fi); |
595 | template <typename Continuation> |
596 | void watchContinuation(const QObject *context, Continuation &&c, QFutureInterfaceBase &fi) |
597 | { |
598 | using Prototype = typename QtPrivate::Callable<Continuation>::Function; |
599 | watchContinuationImpl(context, |
600 | QtPrivate::makeCallableObject<Prototype>(std::forward<Continuation>(c)), |
601 | fi); |
602 | } |
603 | |
604 | template<typename Function, typename ResultType, typename ParentResultType> |
605 | template<typename F> |
606 | void Continuation<Function, ResultType, ParentResultType>::create(F &&func, |
607 | QFuture<ParentResultType> *f, |
608 | QFutureInterface<ResultType> &fi, |
609 | QObject *context) |
610 | { |
611 | Q_ASSERT(f); |
612 | Q_ASSERT(context); |
613 | |
614 | // When the context object is destroyed, the signal-slot connection is broken and the |
615 | // continuation callback is destroyed. The promise that is created in the capture list is |
616 | // destroyed and, if it is not yet finished, cancelled. |
617 | auto continuation = [func = std::forward<F>(func), parent = *f, |
618 | promise = QPromise(fi)]() mutable { |
619 | SyncContinuation<Function, ResultType, ParentResultType> continuationJob( |
620 | std::forward<Function>(func), parent, std::move(promise)); |
621 | continuationJob.execute(); |
622 | }; |
623 | |
624 | QtPrivate::watchContinuation(context, std::move(continuation), f->d); |
625 | } |
626 | |
627 | template<typename Function, typename ResultType, typename ParentResultType> |
628 | void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithResult() |
629 | { |
630 | if constexpr (std::is_copy_constructible_v<ParentResultType>) |
631 | fulfillPromise(parentFuture.result()); |
632 | else |
633 | fulfillPromise(parentFuture.takeResult()); |
634 | } |
635 | |
636 | template<typename Function, typename ResultType, typename ParentResultType> |
637 | void Continuation<Function, ResultType, ParentResultType>::fulfillVoidPromise() |
638 | { |
639 | if constexpr (std::is_copy_constructible_v<ParentResultType>) |
640 | function(parentFuture.result()); |
641 | else |
642 | function(parentFuture.takeResult()); |
643 | } |
644 | |
645 | template<typename Function, typename ResultType, typename ParentResultType> |
646 | void Continuation<Function, ResultType, ParentResultType>::fulfillPromiseWithVoidResult() |
647 | { |
648 | if constexpr (std::is_invocable_v<Function, QFuture<void>>) |
649 | fulfillPromise(parentFuture); |
650 | else |
651 | fulfillPromise(); |
652 | } |
653 | |
654 | template<typename Function, typename ResultType, typename ParentResultType> |
655 | template<class... Args> |
656 | void Continuation<Function, ResultType, ParentResultType>::fulfillPromise(Args &&... args) |
657 | { |
658 | promise.addResult(std::invoke(function, std::forward<Args>(args)...)); |
659 | } |
660 | |
661 | template<class T> |
662 | void fulfillPromise(QPromise<T> &promise, QFuture<T> &future) |
663 | { |
664 | if constexpr (!std::is_void_v<T>) { |
665 | if constexpr (std::is_copy_constructible_v<T>) |
666 | promise.addResult(future.result()); |
667 | else |
668 | promise.addResult(future.takeResult()); |
669 | } |
670 | } |
671 | |
672 | template<class T, class Function> |
673 | void fulfillPromise(QPromise<T> &promise, Function &&handler) |
674 | { |
675 | if constexpr (std::is_void_v<T>) |
676 | handler(); |
677 | else |
678 | promise.addResult(handler()); |
679 | } |
680 | |
681 | #ifndef QT_NO_EXCEPTIONS |
682 | |
683 | template<class Function, class ResultType> |
684 | template<class F> |
685 | void FailureHandler<Function, ResultType>::create(F &&function, QFuture<ResultType> *future, |
686 | const QFutureInterface<ResultType> &fi) |
687 | { |
688 | Q_ASSERT(future); |
689 | |
690 | auto failureContinuation = [function = std::forward<F>(function), promise = QPromise(fi)]( |
691 | const QFutureInterfaceBase &parentData) mutable { |
692 | const auto parent = QFutureInterface<ResultType>(parentData).future(); |
693 | FailureHandler<Function, ResultType> failureHandler(std::forward<Function>(function), |
694 | parent, std::move(promise)); |
695 | failureHandler.run(); |
696 | }; |
697 | |
698 | future->d.setContinuation(ContinuationWrapper(std::move(failureContinuation))); |
699 | } |
700 | |
701 | template<class Function, class ResultType> |
702 | template<class F> |
703 | void FailureHandler<Function, ResultType>::create(F &&function, QFuture<ResultType> *future, |
704 | QFutureInterface<ResultType> &fi, |
705 | QObject *context) |
706 | { |
707 | Q_ASSERT(future); |
708 | Q_ASSERT(context); |
709 | auto failureContinuation = [function = std::forward<F>(function), |
710 | parent = *future, promise = QPromise(fi)]() mutable { |
711 | FailureHandler<Function, ResultType> failureHandler( |
712 | std::forward<Function>(function), parent, std::move(promise)); |
713 | failureHandler.run(); |
714 | }; |
715 | |
716 | QtPrivate::watchContinuation(context, std::move(failureContinuation), future->d); |
717 | } |
718 | |
719 | template<class Function, class ResultType> |
720 | void FailureHandler<Function, ResultType>::run() |
721 | { |
722 | Q_ASSERT(parentFuture.isFinished()); |
723 | |
724 | promise.start(); |
725 | |
726 | if (parentFuture.d.hasException()) { |
727 | using ArgType = typename QtPrivate::ArgResolver<Function>::First; |
728 | if constexpr (std::is_void_v<ArgType>) { |
729 | handleAllExceptions(); |
730 | } else { |
731 | handleException<ArgType>(); |
732 | } |
733 | } else if (parentFuture.d.isChainCanceled()) { |
734 | promise.future().cancel(); |
735 | } else { |
736 | QtPrivate::fulfillPromise(promise, parentFuture); |
737 | } |
738 | promise.finish(); |
739 | } |
740 | |
741 | template<class Function, class ResultType> |
742 | template<class ArgType> |
743 | void FailureHandler<Function, ResultType>::handleException() |
744 | { |
745 | try { |
746 | Q_ASSERT(parentFuture.d.hasException()); |
747 | parentFuture.d.exceptionStore().rethrowException(); |
748 | } catch (const ArgType &e) { |
749 | try { |
750 | // Handle exceptions matching with the handler's argument type |
751 | if constexpr (std::is_void_v<ResultType>) |
752 | handler(e); |
753 | else |
754 | promise.addResult(handler(e)); |
755 | } catch (...) { |
756 | promise.setException(std::current_exception()); |
757 | } |
758 | } catch (...) { |
759 | // Exception doesn't match with handler's argument type, propagate |
760 | // the exception to be handled later. |
761 | promise.setException(std::current_exception()); |
762 | } |
763 | } |
764 | |
765 | template<class Function, class ResultType> |
766 | void FailureHandler<Function, ResultType>::handleAllExceptions() |
767 | { |
768 | try { |
769 | Q_ASSERT(parentFuture.d.hasException()); |
770 | parentFuture.d.exceptionStore().rethrowException(); |
771 | } catch (...) { |
772 | try { |
773 | QtPrivate::fulfillPromise(promise, std::forward<Function>(handler)); |
774 | } catch (...) { |
775 | promise.setException(std::current_exception()); |
776 | } |
777 | } |
778 | } |
779 | |
780 | #endif // QT_NO_EXCEPTIONS |
781 | |
782 | template<class Function, class ResultType> |
783 | class CanceledHandler |
784 | { |
785 | public: |
786 | template<class F = Function> |
787 | static void create(F &&handler, QFuture<ResultType> *future, QFutureInterface<ResultType> &fi) |
788 | { |
789 | Q_ASSERT(future); |
790 | |
791 | auto canceledContinuation = [promise = QPromise(fi), handler = std::forward<F>(handler)]( |
792 | const QFutureInterfaceBase &parentData) mutable { |
793 | auto parentFuture = QFutureInterface<ResultType>(parentData).future(); |
794 | run(std::forward<F>(handler), parentFuture, std::move(promise)); |
795 | }; |
796 | future->d.setContinuation(ContinuationWrapper(std::move(canceledContinuation))); |
797 | } |
798 | |
799 | template<class F = Function> |
800 | static void create(F &&handler, QFuture<ResultType> *future, QFutureInterface<ResultType> &fi, |
801 | QObject *context) |
802 | { |
803 | Q_ASSERT(future); |
804 | Q_ASSERT(context); |
805 | auto canceledContinuation = [handler = std::forward<F>(handler), |
806 | parentFuture = *future, promise = QPromise(fi)]() mutable { |
807 | run(std::forward<F>(handler), parentFuture, std::move(promise)); |
808 | }; |
809 | |
810 | QtPrivate::watchContinuation(context, std::move(canceledContinuation), future->d); |
811 | } |
812 | |
813 | template<class F = Function> |
814 | static void run(F &&handler, QFuture<ResultType> &parentFuture, QPromise<ResultType> &&promise) |
815 | { |
816 | promise.start(); |
817 | |
818 | if (parentFuture.isCanceled()) { |
819 | #ifndef QT_NO_EXCEPTIONS |
820 | if (parentFuture.d.hasException()) { |
821 | // Propagate the exception to the result future |
822 | promise.setException(parentFuture.d.exceptionStore().exception()); |
823 | } else { |
824 | try { |
825 | #endif |
826 | QtPrivate::fulfillPromise(promise, std::forward<F>(handler)); |
827 | #ifndef QT_NO_EXCEPTIONS |
828 | } catch (...) { |
829 | promise.setException(std::current_exception()); |
830 | } |
831 | } |
832 | #endif |
833 | } else { |
834 | QtPrivate::fulfillPromise(promise, parentFuture); |
835 | } |
836 | |
837 | promise.finish(); |
838 | } |
839 | }; |
840 | |
841 | struct UnwrapHandler |
842 | { |
843 | template<class T> |
844 | static auto unwrapImpl(T *outer) |
845 | { |
846 | Q_ASSERT(outer); |
847 | |
848 | using ResultType = typename QtPrivate::Future<std::decay_t<T>>::type; |
849 | using NestedType = typename QtPrivate::Future<ResultType>::type; |
850 | QFutureInterface<NestedType> promise(QFutureInterfaceBase::State::Pending); |
851 | |
852 | outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { |
853 | // We use the .then([](QFuture<ResultType> outerFuture) {...}) version |
854 | // (where outerFuture == *outer), to propagate the exception if the |
855 | // outer future has failed. |
856 | Q_ASSERT(outerFuture.isFinished()); |
857 | #ifndef QT_NO_EXCEPTIONS |
858 | if (outerFuture.d.hasException()) { |
859 | promise.reportStarted(); |
860 | promise.reportException(outerFuture.d.exceptionStore().exception()); |
861 | promise.reportFinished(); |
862 | return; |
863 | } |
864 | #endif |
865 | |
866 | promise.reportStarted(); |
867 | ResultType nestedFuture = outerFuture.result(); |
868 | |
869 | nestedFuture.then([promise] (const QFuture<NestedType> &nested) mutable { |
870 | #ifndef QT_NO_EXCEPTIONS |
871 | if (nested.d.hasException()) { |
872 | promise.reportException(nested.d.exceptionStore().exception()); |
873 | } else |
874 | #endif |
875 | { |
876 | if constexpr (!std::is_void_v<NestedType>) |
877 | promise.reportResults(nested.results()); |
878 | } |
879 | promise.reportFinished(); |
880 | }).onCanceled([promise] () mutable { |
881 | promise.reportCanceled(); |
882 | promise.reportFinished(); |
883 | }); |
884 | }).onCanceled([promise]() mutable { |
885 | // propagate the cancellation of the outer future |
886 | promise.reportStarted(); |
887 | promise.reportCanceled(); |
888 | promise.reportFinished(); |
889 | }); |
890 | return promise.future(); |
891 | } |
892 | }; |
893 | |
894 | template<typename ValueType> |
895 | QFuture<ValueType> makeReadyRangeFutureImpl(const QList<ValueType> &values) |
896 | { |
897 | QFutureInterface<ValueType> promise; |
898 | promise.reportStarted(); |
899 | promise.reportResults(values); |
900 | promise.reportFinished(); |
901 | return promise.future(); |
902 | } |
903 | |
904 | } // namespace QtPrivate |
905 | |
906 | namespace QtFuture { |
907 | |
908 | template<class Signal> |
909 | using ArgsType = typename QtPrivate::ArgResolver<Signal>::AllArgs; |
910 | |
911 | template<class Sender, class Signal, typename = QtPrivate::EnableIfInvocable<Sender, Signal>> |
912 | static QFuture<ArgsType<Signal>> connect(Sender *sender, Signal signal) |
913 | { |
914 | using ArgsType = ArgsType<Signal>; |
915 | QFutureInterface<ArgsType> promise; |
916 | promise.reportStarted(); |
917 | if (!sender) { |
918 | promise.reportCanceled(); |
919 | promise.reportFinished(); |
920 | return promise.future(); |
921 | } |
922 | |
923 | using Connections = std::pair<QMetaObject::Connection, QMetaObject::Connection>; |
924 | auto connections = std::make_shared<Connections>(); |
925 | |
926 | if constexpr (std::is_void_v<ArgsType>) { |
927 | connections->first = |
928 | QObject::connect(sender, signal, sender, [promise, connections]() mutable { |
929 | QObject::disconnect(connections->first); |
930 | QObject::disconnect(connections->second); |
931 | promise.reportFinished(); |
932 | }); |
933 | } else if constexpr (QtPrivate::ArgResolver<Signal>::HasExtraArgs) { |
934 | connections->first = QObject::connect(sender, signal, sender, |
935 | [promise, connections](auto... values) mutable { |
936 | QObject::disconnect(connections->first); |
937 | QObject::disconnect(connections->second); |
938 | promise.reportResult(QtPrivate::createTuple( |
939 | std::move(values)...)); |
940 | promise.reportFinished(); |
941 | }); |
942 | } else { |
943 | connections->first = QObject::connect(sender, signal, sender, |
944 | [promise, connections](ArgsType value) mutable { |
945 | QObject::disconnect(connections->first); |
946 | QObject::disconnect(connections->second); |
947 | promise.reportResult(value); |
948 | promise.reportFinished(); |
949 | }); |
950 | } |
951 | |
952 | if (!connections->first) { |
953 | promise.reportCanceled(); |
954 | promise.reportFinished(); |
955 | return promise.future(); |
956 | } |
957 | |
958 | connections->second = |
959 | QObject::connect(sender, &QObject::destroyed, sender, [promise, connections]() mutable { |
960 | QObject::disconnect(connections->first); |
961 | QObject::disconnect(connections->second); |
962 | promise.reportCanceled(); |
963 | promise.reportFinished(); |
964 | }); |
965 | |
966 | return promise.future(); |
967 | } |
968 | |
969 | template<typename Container> |
970 | using if_container_with_input_iterators = |
971 | std::enable_if_t<QtPrivate::HasInputIterator<Container>::value, bool>; |
972 | |
973 | template<typename Container> |
974 | using ContainedType = |
975 | typename std::iterator_traits<decltype( |
976 | std::cbegin(std::declval<Container&>()))>::value_type; |
977 | |
978 | template<typename Container, if_container_with_input_iterators<Container> = true> |
979 | static QFuture<ContainedType<Container>> makeReadyRangeFuture(Container &&container) |
980 | { |
981 | // handle QList<T> separately, because reportResults() takes a QList |
982 | // as an input |
983 | using ValueType = ContainedType<Container>; |
984 | if constexpr (std::is_convertible_v<q20::remove_cvref_t<Container>, QList<ValueType>>) { |
985 | return QtPrivate::makeReadyRangeFutureImpl(container); |
986 | } else { |
987 | return QtPrivate::makeReadyRangeFutureImpl(QList<ValueType>{std::cbegin(container), |
988 | std::cend(container)}); |
989 | } |
990 | } |
991 | |
992 | template<typename ValueType> |
993 | static QFuture<ValueType> makeReadyRangeFuture(std::initializer_list<ValueType> values) |
994 | { |
995 | return QtPrivate::makeReadyRangeFutureImpl(QList<ValueType>{values}); |
996 | } |
997 | |
998 | template<typename T> |
999 | static QFuture<std::decay_t<T>> makeReadyValueFuture(T &&value) |
1000 | { |
1001 | QFutureInterface<std::decay_t<T>> promise; |
1002 | promise.reportStarted(); |
1003 | promise.reportResult(std::forward<T>(value)); |
1004 | promise.reportFinished(); |
1005 | |
1006 | return promise.future(); |
1007 | } |
1008 | |
1009 | Q_CORE_EXPORT QFuture<void> makeReadyVoidFuture(); // implemented in qfutureinterface.cpp |
1010 | |
1011 | #if QT_DEPRECATED_SINCE(6, 10) |
1012 | template<typename T, typename = QtPrivate::EnableForNonVoid<T>> |
1013 | QT_DEPRECATED_VERSION_X(6, 10, "Use makeReadyValueFuture() instead" ) |
1014 | static QFuture<std::decay_t<T>> makeReadyFuture(T &&value) |
1015 | { |
1016 | return makeReadyValueFuture(std::forward<T>(value)); |
1017 | } |
1018 | |
1019 | // the void specialization is moved to the end of qfuture.h, because it now |
1020 | // uses makeReadyVoidFuture() and required QFuture<void> to be defined. |
1021 | |
1022 | template<typename T> |
1023 | QT_DEPRECATED_VERSION_X(6, 10, "Use makeReadyRangeFuture() instead" ) |
1024 | static QFuture<T> makeReadyFuture(const QList<T> &values) |
1025 | { |
1026 | return makeReadyRangeFuture(values); |
1027 | } |
1028 | #endif // QT_DEPRECATED_SINCE(6, 10) |
1029 | |
1030 | #ifndef QT_NO_EXCEPTIONS |
1031 | |
1032 | template<typename T = void> |
1033 | static QFuture<T> makeExceptionalFuture(std::exception_ptr exception) |
1034 | { |
1035 | QFutureInterface<T> promise; |
1036 | promise.reportStarted(); |
1037 | promise.reportException(exception); |
1038 | promise.reportFinished(); |
1039 | |
1040 | return promise.future(); |
1041 | } |
1042 | |
1043 | template<typename T = void> |
1044 | static QFuture<T> makeExceptionalFuture(const QException &exception) |
1045 | { |
1046 | try { |
1047 | exception.raise(); |
1048 | } catch (...) { |
1049 | return makeExceptionalFuture<T>(std::current_exception()); |
1050 | } |
1051 | Q_UNREACHABLE(); |
1052 | } |
1053 | |
1054 | #endif // QT_NO_EXCEPTIONS |
1055 | |
1056 | } // namespace QtFuture |
1057 | |
1058 | namespace QtPrivate { |
1059 | |
1060 | template<typename ResultFutures> |
1061 | struct WhenAllContext |
1062 | { |
1063 | using ValueType = typename ResultFutures::value_type; |
1064 | |
1065 | explicit WhenAllContext(qsizetype size) : remaining(size) {} |
1066 | |
1067 | template<typename T = ValueType> |
1068 | void checkForCompletion(qsizetype index, T &&future) |
1069 | { |
1070 | futures[index] = std::forward<T>(future); |
1071 | const auto oldRemaining = remaining.fetchAndSubRelaxed(valueToAdd: 1); |
1072 | Q_ASSERT(oldRemaining > 0); |
1073 | if (oldRemaining <= 1) { // that was the last one |
1074 | promise.addResult(futures); |
1075 | promise.finish(); |
1076 | } |
1077 | } |
1078 | |
1079 | QAtomicInteger<qsizetype> remaining; |
1080 | QPromise<ResultFutures> promise; |
1081 | ResultFutures futures; |
1082 | }; |
1083 | |
1084 | template<typename ResultType> |
1085 | struct WhenAnyContext |
1086 | { |
1087 | using ValueType = ResultType; |
1088 | |
1089 | template<typename T = ResultType, typename = EnableForNonVoid<T>> |
1090 | void checkForCompletion(qsizetype, T &&result) |
1091 | { |
1092 | if (!ready.fetchAndStoreRelaxed(newValue: true)) { |
1093 | promise.addResult(std::forward<T>(result)); |
1094 | promise.finish(); |
1095 | } |
1096 | } |
1097 | |
1098 | QAtomicInt ready = false; |
1099 | QPromise<ResultType> promise; |
1100 | }; |
1101 | |
1102 | template<qsizetype Index, typename ContextType, typename... Ts> |
1103 | void addCompletionHandlersImpl(const std::shared_ptr<ContextType> &context, |
1104 | const std::tuple<Ts...> &t) |
1105 | { |
1106 | auto future = std::get<Index>(t); |
1107 | using ResultType = typename ContextType::ValueType; |
1108 | // Need context=context so that the compiler does not infer the captured variable's type as 'const' |
1109 | future.then([context=context](const std::tuple_element_t<Index, std::tuple<Ts...>> &f) { |
1110 | context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, f }); |
1111 | }).onCanceled([context=context, future]() { |
1112 | context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, future }); |
1113 | }); |
1114 | |
1115 | if constexpr (Index != 0) |
1116 | addCompletionHandlersImpl<Index - 1, ContextType, Ts...>(context, t); |
1117 | } |
1118 | |
1119 | template<typename ContextType, typename... Ts> |
1120 | void addCompletionHandlers(const std::shared_ptr<ContextType> &context, const std::tuple<Ts...> &t) |
1121 | { |
1122 | constexpr qsizetype size = std::tuple_size<std::tuple<Ts...>>::value; |
1123 | addCompletionHandlersImpl<size - 1, ContextType, Ts...>(context, t); |
1124 | } |
1125 | |
1126 | template<typename OutputSequence, typename InputIt, typename ValueType> |
1127 | QFuture<OutputSequence> whenAllImpl(InputIt first, InputIt last) |
1128 | { |
1129 | const qsizetype size = std::distance(first, last); |
1130 | if (size == 0) |
1131 | return QtFuture::makeReadyValueFuture(OutputSequence()); |
1132 | |
1133 | const auto context = std::make_shared<QtPrivate::WhenAllContext<OutputSequence>>(size); |
1134 | context->futures.resize(size); |
1135 | context->promise.start(); |
1136 | |
1137 | qsizetype idx = 0; |
1138 | for (auto it = first; it != last; ++it, ++idx) { |
1139 | // Need context=context so that the compiler does not infer the captured variable's type as 'const' |
1140 | it->then([context=context, idx](const ValueType &f) { |
1141 | context->checkForCompletion(idx, f); |
1142 | }).onCanceled([context=context, idx, f = *it] { |
1143 | context->checkForCompletion(idx, f); |
1144 | }); |
1145 | } |
1146 | return context->promise.future(); |
1147 | } |
1148 | |
1149 | template<typename OutputSequence, typename... Futures> |
1150 | QFuture<OutputSequence> whenAllImpl(Futures &&... futures) |
1151 | { |
1152 | constexpr qsizetype size = sizeof...(Futures); |
1153 | const auto context = std::make_shared<QtPrivate::WhenAllContext<OutputSequence>>(size); |
1154 | context->futures.resize(size); |
1155 | context->promise.start(); |
1156 | |
1157 | QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward<Futures>(futures)...)); |
1158 | |
1159 | return context->promise.future(); |
1160 | } |
1161 | |
1162 | template<typename InputIt, typename ValueType> |
1163 | QFuture<QtFuture::WhenAnyResult<typename Future<ValueType>::type>> whenAnyImpl(InputIt first, |
1164 | InputIt last) |
1165 | { |
1166 | using PackagedType = typename Future<ValueType>::type; |
1167 | using ResultType = QtFuture::WhenAnyResult<PackagedType>; |
1168 | |
1169 | const qsizetype size = std::distance(first, last); |
1170 | if (size == 0) { |
1171 | return QtFuture::makeReadyValueFuture( |
1172 | QtFuture::WhenAnyResult { qsizetype(-1), QFuture<PackagedType>() }); |
1173 | } |
1174 | |
1175 | const auto context = std::make_shared<QtPrivate::WhenAnyContext<ResultType>>(); |
1176 | context->promise.start(); |
1177 | |
1178 | qsizetype idx = 0; |
1179 | for (auto it = first; it != last; ++it, ++idx) { |
1180 | // Need context=context so that the compiler does not infer the captured variable's type as 'const' |
1181 | it->then([context=context, idx](const ValueType &f) { |
1182 | context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f }); |
1183 | }).onCanceled([context=context, idx, f = *it] { |
1184 | context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f }); |
1185 | }); |
1186 | } |
1187 | return context->promise.future(); |
1188 | } |
1189 | |
1190 | template<typename... Futures> |
1191 | QFuture<std::variant<std::decay_t<Futures>...>> whenAnyImpl(Futures &&... futures) |
1192 | { |
1193 | using ResultType = std::variant<std::decay_t<Futures>...>; |
1194 | |
1195 | const auto context = std::make_shared<QtPrivate::WhenAnyContext<ResultType>>(); |
1196 | context->promise.start(); |
1197 | |
1198 | QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward<Futures>(futures)...)); |
1199 | |
1200 | return context->promise.future(); |
1201 | } |
1202 | |
1203 | } // namespace QtPrivate |
1204 | |
1205 | QT_END_NAMESPACE |
1206 | |