diff options
author | Matt Diener <matt.diener@mongodb.com> | 2022-05-26 16:31:24 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-05-26 17:49:43 +0000 |
commit | 4aad7bee6c9605480c55cd6fe274fc693654b7bc (patch) | |
tree | 4f436e2c93e81f5f7a3430137b45f4867cb434af /src/mongo/util | |
parent | a5dd1d90a957e17f954b2df97d3da4a3644e166f (diff) | |
download | mongo-4aad7bee6c9605480c55cd6fe274fc693654b7bc.tar.gz |
SERVER-66128 Add Policy to Future Func-using API
Diffstat (limited to 'src/mongo/util')
-rw-r--r-- | src/mongo/util/future.h | 239 | ||||
-rw-r--r-- | src/mongo/util/future_impl.h | 110 |
2 files changed, 256 insertions, 93 deletions
diff --git a/src/mongo/util/future.h b/src/mongo/util/future.h index cfdb724a101..57e560f0cf2 100644 --- a/src/mongo/util/future.h +++ b/src/mongo/util/future.h @@ -369,10 +369,11 @@ public: * * TODO decide how to handle func throwing. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>) - void getAsync(Func&& func) && noexcept { - std::move(this->_impl).getAsync(std::forward<Func>(func)); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>&& + isFuturePolicy<Policy>) + void getAsync(Policy policy, Func&& func) && noexcept { + std::move(this->_impl).getAsync(policy, std::forward<Func>(func)); } // @@ -410,10 +411,10 @@ public: * The callback takes a T and can return anything (see above for how Statusy and Futurey returns * are handled.) */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable<Func, T>) - /*see above*/ auto then(Func&& func) && noexcept { - return wrap<Func, T>(std::move(this->_impl).then(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable<Func, T>&& isFuturePolicy<Policy>) + /*see above*/ auto then(Policy policy, Func&& func) && noexcept { + return wrap<Func, T>(std::move(this->_impl).then(policy, std::forward<Func>(func))); } /** @@ -423,10 +424,11 @@ public: * The callback takes a StatusOrStatusWith<T> and can return anything (see above for how Statusy * and Futurey returns are handled.) */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>) - /*see above*/ auto onCompletion(Func&& func) && noexcept { - return wrap<Func, Status>(std::move(this->_impl).onCompletion(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>&& isFuturePolicy<Policy>) + /*see above*/ auto onCompletion(Policy policy, Func&& func) && noexcept { + return wrap<Func, Status>( + std::move(this->_impl).onCompletion(policy, std::forward<Func>(func))); } /** @@ -442,10 +444,10 @@ public: * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how * Statusy and Futurey returns are handled.) */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - /*see above*/ auto onError(Func&& func) && noexcept { - return wrap<Func, Status>(std::move(this->_impl).onError(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + /*see above*/ auto onError(Policy policy, Func&& func) && noexcept { + return wrap<Func, Status>(std::move(this->_impl).onError(policy, std::forward<Func>(func))); } /** @@ -455,11 +457,11 @@ public: * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how * Statusy and Futurey returns are handled.) */ - TEMPLATE(ErrorCodes::Error code, typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - /*see above*/ auto onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + /*see above*/ auto onError(Policy policy, Func&& func) && noexcept { return wrap<Func, Status>( - std::move(this->_impl).template onError<code>(std::forward<Func>(func))); + std::move(this->_impl).template onError<code>(policy, std::forward<Func>(func))); } /** @@ -469,11 +471,12 @@ public: * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how * Statusy and Futurey returns are handled.) */ - TEMPLATE(ErrorCategory category, typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - /*see above*/ auto onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + /*see above*/ auto onErrorCategory(Policy policy, Func&& func) && noexcept { return wrap<Func, Status>( - std::move(this->_impl).template onErrorCategory<category>(std::forward<Func>(func))); + std::move(this->_impl) + .template onErrorCategory<category>(policy, std::forward<Func>(func))); } // @@ -496,10 +499,10 @@ public: * * The callback takes a const T& and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR<void, Func, const T>) - Future<T> tap(Func&& func) && noexcept { - return Future<T>(std::move(this->_impl).tap(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const T>&& isFuturePolicy<Policy>) + Future<T> tap(Policy policy, Func&& func) && noexcept { + return Future<T>(std::move(this->_impl).tap(policy, std::forward<Func>(func))); } /** @@ -509,10 +512,10 @@ public: * * The callback takes a non-OK Status and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR<void, Func, const Status>) - Future<T> tapError(Func&& func) && noexcept { - return Future<T>(std::move(this->_impl).tapError(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const Status>&& isFuturePolicy<Policy>) + Future<T> tapError(Policy policy, Func&& func) && noexcept { + return Future<T>(std::move(this->_impl).tapError(policy, std::forward<Func>(func))); } /** @@ -523,10 +526,11 @@ public: * * The callback takes a StatusOrStatusWith<T> and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR<void, Func, const StatusOrStatusWith<T>>) - Future<T> tapAll(Func&& func) && noexcept { - return Future<T>(std::move(this->_impl).tapAll(std::forward<Func>(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const StatusOrStatusWith<T>>&& + isFuturePolicy<Policy>) + Future<T> tapAll(Policy policy, Func&& func) && noexcept { + return Future<T>(std::move(this->_impl).tapAll(policy, std::forward<Func>(func))); } // Calling this on a Future is never strictly necessary, since this is already an inline @@ -534,6 +538,59 @@ public: // unsafeToInlineFuture for some future types but not others. using SemiFuture<T>::unsafeToInlineFuture; + /** + * Providing a Policy to continuation functions is optional. All callers should eventually + * use the default policy by removing the tag altogether. + * + * TODO(SERVER-66126): Remove all tag-taking methods and associated default implementations. + */ + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>) + void getAsync(Func&& func) && noexcept { + std::move(*this).getAsync(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable<Func, T>) + auto then(Func&& func) && noexcept { + return std::move(*this).then(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>) + auto onCompletion(Func&& func) && noexcept { + return std::move(*this).onCompletion(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onError(Func&& func) && noexcept { + return std::move(*this).onError(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(ErrorCodes::Error code, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onError(Func&& func) && noexcept { + return std::move(*this).template onError<code>(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(ErrorCategory category, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onErrorCategory(Func&& func) && noexcept { + return std::move(*this).template onErrorCategory<category>(destroyDefault, + std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const T>) + Future<T> tap(Func&& func) && noexcept { + return std::move(*this).tap(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const Status>) + Future<T> tapError(Func&& func) && noexcept { + return std::move(*this).tapError(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, const StatusOrStatusWith<T>>) + Future<T> tapAll(Func&& func) && noexcept { + return std::move(*this).tapAll(destroyDefault, std::forward<Func>(func)); + } + private: template <typename> friend class ExecutorFuture; @@ -636,15 +693,16 @@ public: * Attach a completion callback to asynchronously consume this `ExecutorFuture`'s result. * \see `Future<T>::getAsync()`. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>) - void getAsync(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>&& + isFuturePolicy<Policy>) + void getAsync(Policy policy, Func&& func) && noexcept { static_assert(std::is_void_v<decltype(func(std::declval<StatusOrStatusWith<T>>()))>, "func passed to getAsync must return void"); // Can't use wrapCB since we don't want to return a future, just schedule a non-chainable // callback. - std::move(this->_impl).getAsync([ + std::move(this->_impl).getAsync(policy, [ exec = std::move(_exec), // Unlike wrapCB this can move because we won't need it later. func = std::forward<Func>(func) ](StatusOrStatusWith<T> arg) mutable noexcept { @@ -656,46 +714,50 @@ public: }); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable<Func, T>) - auto then(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable<Func, T>&& isFuturePolicy<Policy>) + auto then(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( - std::move(_exec), std::move(this->_impl).then(wrapCB<T>(std::forward<Func>(func)))); + std::move(_exec), + std::move(this->_impl).then(policy, wrapCB<T>(policy, std::forward<Func>(func)))); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>) - auto onCompletion(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>&& isFuturePolicy<Policy>) + auto onCompletion(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .onCompletion(wrapCB<StatusOrStatusWith<T>>(std::forward<Func>(func)))); + .onCompletion(policy, + wrapCB<StatusOrStatusWith<T>>(policy, std::forward<Func>(func)))); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - ExecutorFuture<T> onError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + ExecutorFuture<T> onError(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), - std::move(this->_impl).onError(wrapCB<Status>(std::forward<Func>(func)))); + std::move(this->_impl) + .onError(policy, wrapCB<Status>(policy, std::forward<Func>(func)))); } - TEMPLATE(ErrorCodes::Error code, typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - ExecutorFuture<T> onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + ExecutorFuture<T> onError(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .template onError<code>(wrapCB<Status>(std::forward<Func>(func)))); + .template onError<code>(policy, wrapCB<Status>(policy, std::forward<Func>(func)))); } - TEMPLATE(ErrorCategory category, typename Func) - REQUIRES(future_details::isCallableR<T, Func, Status>) - ExecutorFuture<T> onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>&& isFuturePolicy<Policy>) + ExecutorFuture<T> onErrorCategory(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .template onErrorCategory<category>(wrapCB<Status>(std::forward<Func>(func)))); + .template onErrorCategory<category>( + policy, wrapCB<Status>(policy, std::forward<Func>(func)))); } /** @@ -706,6 +768,44 @@ public: */ using SemiFuture<T>::unsafeToInlineFuture; + /** + * Providing a Policy to continuation functions is optional. All callers should eventually + * use the default policy by removing the tag altogether. + * + * TODO(SERVER-66126): Remove all tag-taking methods and associated default implementations. + */ + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR<void, Func, StatusOrStatusWith<T>>) + void getAsync(Func&& func) && noexcept { + std::move(*this).getAsync(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable<Func, T>) + auto then(Func&& func) && noexcept { + return std::move(*this).then(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable<Func, StatusOrStatusWith<T>>) + auto onCompletion(Func&& func) && noexcept { + return std::move(*this).onCompletion(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onError(Func&& func) && noexcept { + return std::move(*this).onError(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(ErrorCodes::Error code, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onError(Func&& func) && noexcept { + return std::move(*this).template onError<code>(destroyDefault, std::forward<Func>(func)); + } + TEMPLATE(ErrorCategory category, typename Func) + REQUIRES(future_details::isCallableR<T, Func, Status>) + auto onErrorCategory(Func&& func) && noexcept { + return std::move(*this).template onErrorCategory<category>(destroyDefault, + std::forward<Func>(func)); + } + private: // This *must* take exec by ref to ensure it isn't moved from while evaluating wrapCB above. ExecutorFuture(ExecutorPtr&& exec, Impl&& impl) : SemiFuture<T>(std::move(impl)), _exec(exec) { @@ -717,8 +817,9 @@ private: * Future<U>, then schedules a task on _exec to complete the associated promise with the result * of calling func with that argument. */ - template <typename RawArg, typename Func> - auto wrapCB(Func&& func) { + TEMPLATE(typename RawArg, typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + auto wrapCB(Policy policy, Func&& func) { // Have to take care to never put void in argument position, since that is a hard error. using Result = typename std::conditional_t<std::is_void_v<RawArg>, std::invoke_result<Func>, @@ -828,10 +929,15 @@ public: * because this method will correctly propagate errors thrown from makeResult(), rather than * ErrorCodes::BrokenPromise. */ + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, void>&& isFuturePolicy<Policy>) + void setWith(Policy policy, Func&& func) noexcept { + setFrom(Future<void>::makeReady().then(policy, std::forward<Func>(func))); + } TEMPLATE(typename Func) REQUIRES(future_details::isCallableR<T, Func, void>) void setWith(Func&& func) noexcept { - setFrom(Future<void>::makeReady().then(std::forward<Func>(func))); + setWith(destroyDefault, std::forward<Func>(func)); } /** @@ -1116,10 +1222,15 @@ public: return SharedSemiFuture<T>(_sharedState); } + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR<T, Func, void>&& isFuturePolicy<Policy>) + void setWith(Policy policy, Func&& func) noexcept { + setFrom(Future<void>::makeReady().then(policy, std::forward<Func>(func))); + } TEMPLATE(typename Func) REQUIRES(future_details::isCallableR<T, Func, void>) void setWith(Func&& func) noexcept { - setFrom(Future<void>::makeReady().then(std::forward<Func>(func))); + setWith(destroyDefault, std::forward<Func>(func)); } /** diff --git a/src/mongo/util/future_impl.h b/src/mongo/util/future_impl.h index b1ef0ffd955..a2a5d963675 100644 --- a/src/mongo/util/future_impl.h +++ b/src/mongo/util/future_impl.h @@ -52,6 +52,47 @@ namespace mongo { +struct FuturePolicy {}; + +template <typename T> +inline constexpr bool isFuturePolicy = std::is_base_of_v<FuturePolicy, T>; + +/** + * Transitional tags for specifying destruction order semantics for continuations. + * The long-term goal is to eliminate `destroyWeak` via manual review of existing + * continuations. + * A continuation can be switched to `destroyStrong` when it has been determined + * that subsequent continuations do not depend on the lifetime of its captures. + * The plan is to remove these transitional tags altogether after _all_ continuations + * have been thus converted to the strong semantics specification. + */ +template <bool strongCleanupValue> +struct CleanupFuturePolicy : FuturePolicy { + static constexpr bool strongCleanup = strongCleanupValue; +}; +using WeakFuturePolicy = CleanupFuturePolicy<false>; +using StrongFuturePolicy = CleanupFuturePolicy<true>; + +/** + * The passed-in continuation function may or may not be cleared + * immediately after the function runs. In some contexts the entire + * continuation chain will run and callbacks are destroyed as the stack + * unwinds. In other contexts, each stage of the continuation will destroy its + * callback immediately following execution. + */ +inline constexpr WeakFuturePolicy destroyWeak{}; + +/** + * The passed-in continuation function will always be cleared + * immediately after the function runs, and before the subsequent continuation runs. + */ +inline constexpr StrongFuturePolicy destroyStrong{}; + +/** + * Used by Future implementation details to apply a consistent default FuturePolicy. + */ +inline constexpr WeakFuturePolicy destroyDefault{}; + template <typename T> class Promise; @@ -881,8 +922,9 @@ public: return _shared.getNoThrow(interruptible); } - template <typename Func> - void getAsync(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + void getAsync(Policy policy, Func&& func) && noexcept { static_assert(std::is_void<decltype(call(func, std::declval<StatusWith<T>>()))>::value, "func passed to getAsync must return void"); @@ -905,8 +947,9 @@ public: }); } - template <typename Func> - auto then(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + auto then(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult<Func, T>; if constexpr (!isFutureLike<Result>) { return generalImpl( @@ -959,8 +1002,9 @@ public: } } - template <typename Func> - auto onCompletion(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + auto onCompletion(Policy policy, Func&& func) && noexcept { using Wrapper = StatusOrStatusWith<T>; using Result = NormalizedCallResult<Func, StatusOrStatusWith<T>>; if constexpr (!isFutureLike<Result>) { @@ -1034,8 +1078,9 @@ public: } } - template <typename Func> - FutureImpl<FakeVoidToVoid<T>> onError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> onError(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult<Func, Status>; static_assert( std::is_same<VoidToFakeVoid<UnwrappedType<Result>>, T>::value, @@ -1088,8 +1133,9 @@ public: } } - template <ErrorCodes::Error code, typename Func> - FutureImpl<FakeVoidToVoid<T>> onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> onError(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult<Func, Status>; static_assert( std::is_same_v<UnwrappedType<Result>, FakeVoidToVoid<T>>, @@ -1100,15 +1146,17 @@ public: // TODO in C++17 with constexpr if this can be done cleaner and more efficiently by not // throwing. - return std::move(*this).onError([func = std::forward<Func>(func)](Status&& status) mutable { - if (status != code) - uassertStatusOK(status); - return throwingCall(func, std::move(status)); - }); + return std::move(*this).onError(policy, + [func = std::forward<Func>(func)](Status&& status) mutable { + if (status != code) + uassertStatusOK(status); + return throwingCall(func, std::move(status)); + }); } - template <ErrorCategory category, typename Func> - FutureImpl<FakeVoidToVoid<T>> onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> onErrorCategory(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult<Func, Status>; static_assert(std::is_same_v<UnwrappedType<Result>, FakeVoidToVoid<T>>, "func passed to Future<T>::onErrorCategory must return T, StatusWith<T>, " @@ -1117,15 +1165,17 @@ public: if (_immediate || (isReady() && _shared->status.isOK())) return std::move(*this); - return std::move(*this).onError([func = std::forward<Func>(func)](Status&& status) mutable { - if (!ErrorCodes::isA<category>(status)) - uassertStatusOK(status); - return throwingCall(func, std::move(status)); - }); + return std::move(*this).onError(policy, + [func = std::forward<Func>(func)](Status&& status) mutable { + if (!ErrorCodes::isA<category>(status)) + uassertStatusOK(status); + return throwingCall(func, std::move(status)); + }); } - template <typename Func> - FutureImpl<FakeVoidToVoid<T>> tap(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> tap(Policy policy, Func&& func) && noexcept { static_assert(std::is_void<decltype(call(func, std::declval<const T&>()))>::value, "func passed to tap must return void"); @@ -1134,8 +1184,9 @@ public: [](Func && func, const Status& status) noexcept {}); } - template <typename Func> - FutureImpl<FakeVoidToVoid<T>> tapError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> tapError(Policy policy, Func&& func) && noexcept { static_assert(std::is_void<decltype(call(func, std::declval<const Status&>()))>::value, "func passed to tapError must return void"); @@ -1143,8 +1194,9 @@ public: ](Func && func, const Status& status) noexcept { call(func, status); }); } - template <typename Func> - FutureImpl<FakeVoidToVoid<T>> tapAll(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy<Policy>) + FutureImpl<FakeVoidToVoid<T>> tapAll(Policy policy, Func&& func) && noexcept { static_assert( std::is_void<decltype(call(func, std::declval<const StatusOrStatusWith<T>&>()))>::value, "func passed to tapAll must return void"); @@ -1340,7 +1392,7 @@ private: template <typename T> inline FutureImpl<void> FutureImpl<T>::ignoreValue() && noexcept { - return std::move(*this).then([](auto&&) {}); + return std::move(*this).then(destroyDefault, [](auto&&) {}); } } // namespace future_details |