From c93740d3c0ee74311c442f55295cd0b9c8bd63cd Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Wed, 10 Jul 2024 17:10:09 -0700 Subject: [PATCH] Make any_sender_of<> play nicer with MSVC `unifex::any_sender_of<>` needs a type-erased operation state, which it creates with `unifex::any_unique`. The tricky bit is that operation states are expected to be immovable so constructing that `any_unique` requires care. The old approach was to use a type called `_rvo<>` that implicitly converted to the desired type, but it was brittle. This change invents a new opstate type with two jobs: 1. wrap some other opstate, and 2. construct that other opstate by invoking a callable. --- include/unifex/any_sender_of.hpp | 56 +++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/include/unifex/any_sender_of.hpp b/include/unifex/any_sender_of.hpp index b182d935..ee6155a8 100644 --- a/include/unifex/any_sender_of.hpp +++ b/include/unifex/any_sender_of.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with @@ -99,18 +99,27 @@ struct _rec_ref::type template using _receiver_ref = typename _rec_ref::type; -// For in-place constructing non-movable operation states. -// Relies on C++17's guaranteed copy elision. -template -struct _rvo { - Sender&& s; - Receiver r; - operator connect_result_t() { - return connect((Sender &&) s, std::move(r)); - } +template +struct _op_wrapper final { + struct type; +}; + +template +struct _op_wrapper::type final { + template(typename Fn) // + (requires std::is_invocable_v) // + explicit type(Fn&& fn) + : op_(std::forward(fn)()) {} + + type(type&&) = delete; + + ~type() = default; + + void start() & noexcept { unifex::start(op_); } + +private: + Op op_; }; -template -_rvo(Sender&&, Receiver) -> _rvo; template struct _connect_fn { @@ -126,9 +135,18 @@ struct _connect_fn::type { (requires sender_to) // friend _operation_state tag_invoke(const type&, Sender&& s, _rec_ref_t r) { - using Op = connect_result_t; - return _operation_state{ - std::in_place_type, _rvo{(Sender &&) s, std::move(r)}}; + // MSVC can't resolve std::forward(s) inside the lambda, but + // saving Sender into sender_t and then invoking std::forward(s) + // works just fine ¯\_(ツ)_/¯ + using sender_t = Sender; + auto cnct = [&]() { + return unifex::connect(std::forward(s), std::move(r)); + }; + + using inner_op_t = decltype(cnct()); + using wrapper_t = typename _op_wrapper::type; + + return _operation_state{std::in_place_type, std::move(cnct)}; } #ifdef _MSC_VER @@ -137,14 +155,14 @@ struct _connect_fn::type { template tag_invoke_result_t operator()(Self&& s, _rec_ref_t r) const { - return tag_invoke(*this, (Self &&) s, std::move(r)); + return tag_invoke(*this, (Self&&)s, std::move(r)); } #else template(typename Self) // (requires tag_invocable) // _operation_state operator()(Self&& s, _rec_ref_t r) const { - return tag_invoke(*this, (Self &&) s, std::move(r)); + return tag_invoke(*this, (Self&&)s, std::move(r)); } #endif }; @@ -164,7 +182,7 @@ template struct _op_for::type { template explicit type(Receiver r, Fn fn) - : rec_((Receiver &&) r) + : rec_((Receiver&&)r) , state_{ fn({subscription_.subscribe(unifex::get_stop_token(rec_)), this})} {} @@ -178,7 +196,7 @@ struct _op_for::type { friend void tag_invoke(CPO cpo, type&& self, Args&&... args) noexcept( std::is_nothrow_invocable_v) { self.subscription_.unsubscribe(); - cpo(std::move(self).rec_, (Args &&) args...); + cpo(std::move(self).rec_, (Args&&)args...); } // Forward other receiver queries