/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace unifex { namespace _let_e { template struct _op { class type; }; template using operation_type = typename _op>::type; template struct _rcvr { class type; }; template using receiver_type = typename _rcvr>::type; template struct _frcvr { class type; }; template using final_receiver_type = typename _frcvr, Error>::type; template struct _sndr { class type; }; template using _sender = typename _sndr::type; template class _rcvr::type final { using operation = operation_type; template using final_receiver = final_receiver_type; public: explicit type(operation* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, {})) {} // Taking by value here to force a move/copy on the offchance the value // objects live in the operation state, in which case destroying the // predecessor operation state would invalidate it. // // Flipping the order of `deactivate_*` and `set_value` is UB since by the // time `set_value()` returns `op_` might as well be already destroyed, // without proper deactivation of `op_->sourceOp_`. template(typename... Values) (requires receiver_of) void set_value(Values... values) noexcept( is_nothrow_receiver_of_v) { // local copy, b/c deactivate_union_member deletes this auto op = op_; UNIFEX_ASSERT(op != nullptr); unifex::deactivate_union_member(op->sourceOp_); unifex::set_value(std::move(op->receiver_), std::move(values)...); } void set_done() noexcept { // local copy, b/c deactivate_union_member deletes this auto op = op_; UNIFEX_ASSERT(op != nullptr); unifex::deactivate_union_member(op->sourceOp_); unifex::set_done(std::move(op->receiver_)); } #if defined(_MSC_VER) && !defined(__clang__) // cl.exe template #else template (typename ErrorValue) (requires callable AND // For some reason, MSVC chokes on this when compiling with real concepts sender_to< callable_result_t, final_receiver>) #endif void set_error(ErrorValue e) noexcept { // local copy, b/c deactivate_union_member deletes this auto op = op_; UNIFEX_ASSERT(op != nullptr); using final_sender_t = callable_result_t; using final_op_t = unifex::connect_result_t>; UNIFEX_TRY { scope_guard destroyPredOp = [&]() noexcept { unifex::deactivate_union_member(op->sourceOp_); }; auto& err = op->error_.template construct((ErrorValue &&) e); destroyPredOp.reset(); scope_guard destroyErr = [&]() noexcept { op->error_.template destruct(); }; auto& finalOp = unifex::activate_union_member_with(op->finalOp_, [&] { return unifex::connect( std::move(op->func_)(err), final_receiver{op}); }); unifex::start(finalOp); destroyErr.release(); } UNIFEX_CATCH(...) { unifex::set_error(std::move(op->receiver_), std::current_exception()); } } private: template(typename CPO, typename Self) (requires is_receiver_query_cpo_v AND same_as, type> AND is_callable_v) friend auto tag_invoke(CPO cpo, Self&& r) noexcept(is_nothrow_callable_v) -> callable_result_t { return std::move(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const type& r, VisitFunc&& func) noexcept(is_nothrow_callable_v< VisitFunc&, const Receiver&>) { func(r.get_receiver()); } const Receiver& get_receiver() const noexcept { UNIFEX_ASSERT(op_ != nullptr); return op_->receiver_; } operation* op_; }; template class _frcvr::type { using operation = operation_type; public: explicit type(operation* op) noexcept : op_(op) {} type(type&& other) noexcept : op_(std::exchange(other.op_, {})) {} template(typename... Values) (requires receiver_of) // Taking by value here to force a move/copy on the offchance the value // objects live in the operation state, in which case destroying the // predecessor operation state would invalidate it. void set_value(Values... values) noexcept( is_nothrow_receiver_of_v) { // local copy, b/c cleanup deletes this auto op = op_; cleanup(op); UNIFEX_TRY { unifex::set_value(std::move(op->receiver_), std::move(values)...); } UNIFEX_CATCH (...) { unifex::set_error(std::move(op->receiver_), std::current_exception()); } } void set_done() noexcept { // local copy, b/c cleanup deletes this auto op = op_; cleanup(op); unifex::set_done(std::move(op->receiver_)); } template(typename ErrorValue) (requires receiver) // Taking by value here to force a copy on the offchance the error // object lives in the operation state, in which // case the call to cleanup() would invalidate them. void set_error(ErrorValue error) noexcept { // local copy, b/c cleanup deletes this auto op = op_; cleanup(op); unifex::set_error(std::move(op->receiver_), std::move(error)); } private: static void cleanup(operation* op) noexcept { using final_sender_t = callable_result_t; using final_op_t = unifex::connect_result_t; UNIFEX_ASSERT(op != nullptr); unifex::deactivate_union_member(op->finalOp_); op->error_.template destruct(); } template(typename CPO) (requires is_receiver_query_cpo_v AND is_callable_v) friend auto tag_invoke(CPO cpo, const type& r) noexcept(is_nothrow_callable_v) -> callable_result_t { return std::move(cpo)(r.get_receiver()); } template friend void tag_invoke( tag_t, const type& r, VisitFunc&& func) noexcept(is_nothrow_callable_v< VisitFunc&, const Receiver&>) { func(r.get_receiver()); } const Receiver& get_receiver() const noexcept { UNIFEX_ASSERT(op_ != nullptr); return op_->receiver_; } operation* op_; }; template class _op::type { using source_receiver = receiver_type; template using final_receiver = final_receiver_type; public: template explicit type(Source&& source, Func2&& func, Receiver2&& dest) noexcept( std::is_nothrow_constructible_v&& std::is_nothrow_constructible_v&& is_nothrow_connectable_v) : func_((Func2 &&) func) , receiver_((Receiver2 &&) dest) { unifex::activate_union_member_with(sourceOp_, [&] { return unifex::connect((Source &&) source, source_receiver{this}); }); } ~type() { if (!started_) { unifex::deactivate_union_member(sourceOp_); } } void start() & noexcept { started_ = true; unifex::start(sourceOp_.get()); } private: friend source_receiver; template < typename Source2, typename Func2, typename Receiver2, typename Error2> friend struct _frcvr; using source_type = remove_cvref_t; using source_op_t = connect_result_t; template using final_sender_t = callable_result_t>; template using final_receiver_t = final_receiver_type; template using final_op_t = connect_result_t, final_receiver_t>; template using final_op_union = manual_lifetime_union...>; using final_op_union_t = sender_error_types_t; UNIFEX_NO_UNIQUE_ADDRESS Func func_; UNIFEX_NO_UNIQUE_ADDRESS Receiver receiver_; UNIFEX_NO_UNIQUE_ADDRESS sender_error_types_t error_; union { manual_lifetime sourceOp_; final_op_union_t finalOp_; }; bool started_ = false; }; template struct sends_done_impl : std::bool_constant::sends_done> {}; template using any_sends_done = std::disjunction...>; template class _sndr::type { template using final_sender = callable_result_t>; using final_senders_list = map_type_list_t, final_sender>; template using source_receiver = receiver_type, Func, Receiver>; template using sends_done_impl = any_sends_done...>; public: template < template class Variant, template class Tuple> using value_types = type_list_nested_apply_t< concat_type_lists_unique_t< sender_value_type_list_t, apply_to_type_list_t< concat_type_lists_unique_t, map_type_list_t>>, Variant, Tuple>; template