diff --git a/include/unifex/let_done.hpp b/include/unifex/let_done.hpp index 17855843f..96b2b2663 100644 --- a/include/unifex/let_done.hpp +++ b/include/unifex/let_done.hpp @@ -219,15 +219,22 @@ class _op::type { public: template - explicit type(Source&& source, Done2&& done, Receiver2&& dest) + explicit type( + Source&& source, // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) + Done2&& done, Receiver2&& dest) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && is_nothrow_connectable_v) : done_((Done2&&)done) , receiver_((Receiver2&&)dest) { + // Note: 'Source' is not a forwarding reference since it's not deduced + // in this constructor. It can either be a Sender&& or Sender& for + // some concrete type Sender. Here, we want the forwarding behavior when + // the operation is constructed based on the type of Source, even though + // it's not a idiomatic use for std::forward. unifex::activate_union_member_with(sourceOp_, [&] { - return unifex::connect((Source&&)source, source_receiver{this}); + return unifex::connect(std::forward(source), source_receiver{this}); }); startedOp_ = 0 + 1; } diff --git a/include/unifex/let_error.hpp b/include/unifex/let_error.hpp index f2ae16f80..727083ccf 100644 --- a/include/unifex/let_error.hpp +++ b/include/unifex/let_error.hpp @@ -257,14 +257,21 @@ class _op::type final { public: template - explicit type(Source&& source, Func2&& func, Receiver2&& dest) noexcept( + explicit type( + Source&& source, // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) + 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) { + // Note: 'Source' is not a forwarding reference since it's not deduced + // in this constructor. It can either be a Sender&& or Sender& for + // some concrete type Sender. Here, we want the forwarding behavior when + // the operation is constructed based on the type of Source, even though + // it's not a idiomatic use for std::forward. unifex::activate_union_member_with(sourceOp_, [&] { - return unifex::connect((Source &&) source, source_receiver{this}); + return unifex::connect(std::forward(source), source_receiver{this}); }); } @@ -418,8 +425,8 @@ class _sndr::type final { is_nothrow_connectable_v, SourceReceiver> && std::is_nothrow_constructible_v> && std::is_nothrow_constructible_v, Receiver>) - -> operation_type { - return operation_type{ + -> operation_type, Func, Receiver> { + return operation_type , Func, Receiver>{ static_cast(s).source_, static_cast(s).func_, static_cast(r) diff --git a/include/unifex/let_value.hpp b/include/unifex/let_value.hpp index 842afb7c1..ea328521b 100644 --- a/include/unifex/let_value.hpp +++ b/include/unifex/let_value.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -85,7 +86,7 @@ struct _successor_receiver::type { void set_error(Error error) && noexcept { auto& op = op_; cleanup(); - unifex::set_error(std::move(op.receiver_), (Error &&) error); + unifex::set_error(std::move(op.receiver_), std::move(error)); } private: @@ -178,7 +179,7 @@ struct _predecessor_receiver::type { void set_error(Error error) && noexcept { auto& op = op_; unifex::deactivate_union_member(op.predOp_); - unifex::set_error(std::move(op.receiver_), (Error &&) error); + unifex::set_error(std::move(op.receiver_), std::move(error)); } template(typename CPO) @@ -227,16 +228,16 @@ struct _op::type { template friend struct _successor_receiver; - template + template explicit type( - Predecessor&& pred, + Predecessor2&& pred, SuccessorFactory2&& func, Receiver2&& receiver) : func_((SuccessorFactory2 &&) func), receiver_((Receiver2 &&) receiver) { unifex::activate_union_member_with(predOp_, [&] { return unifex::connect( - (Predecessor &&) pred, predecessor_receiver{*this}); + std::forward(pred), predecessor_receiver{*this}); }); } diff --git a/include/unifex/let_value_with.hpp b/include/unifex/let_value_with.hpp index 2a13b9634..956abb127 100644 --- a/include/unifex/let_value_with.hpp +++ b/include/unifex/let_value_with.hpp @@ -23,6 +23,8 @@ #include #include +#include + #include namespace unifex { @@ -100,12 +102,12 @@ template struct _operation::type { template type(StateFactory2&& stateFactory, SuccessorFactory2&& func, Receiver2&& r) : - stateFactory_(static_cast(stateFactory)), + stateFactory_(std::forward(stateFactory)), func_(static_cast(func)), - state_(static_cast(stateFactory_)()), + state_(std::move(stateFactory_)()), innerOp_( unifex::connect( - static_cast(func_)(state_), + std::move(func_)(state_), static_cast(r))) { } diff --git a/include/unifex/let_value_with_stop_source.hpp b/include/unifex/let_value_with_stop_source.hpp index 66d4f9bfc..46c6f806d 100644 --- a/include/unifex/let_value_with_stop_source.hpp +++ b/include/unifex/let_value_with_stop_source.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include namespace unifex { @@ -165,7 +167,7 @@ struct _stop_source_operation::type { nothrow_connectable) { return unifex::connect( static_cast(func)(stopSource_), - receiver_t{*this, static_cast(r)}); + receiver_t{*this, std::move(r)}); } public: diff --git a/include/unifex/let_value_with_stop_token.hpp b/include/unifex/let_value_with_stop_token.hpp index bf9a5069e..843c268b2 100644 --- a/include/unifex/let_value_with_stop_token.hpp +++ b/include/unifex/let_value_with_stop_token.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include namespace unifex { diff --git a/test/let_done_test.cpp b/test/let_done_test.cpp index 74f4fe605..c9b643fb0 100644 --- a/test/let_done_test.cpp +++ b/test/let_done_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -104,3 +105,11 @@ TEST(TransformDone, WithValue) { EXPECT_TRUE(multiple.has_value()); EXPECT_EQ(*multiple, std::tuple(42, 1, 2)); } + +TEST(TransformDone, LvalueConnectable) { + int n = 0; + sync_wait(repeat_effect_until( + let_done(just(), [] { return just(); }), + [&n]() mutable noexcept { return n++ == 5; })); + EXPECT_EQ(n, 6); +} diff --git a/test/let_error_test.cpp b/test/let_error_test.cpp index 158717388..59f67cd1e 100644 --- a/test/let_error_test.cpp +++ b/test/let_error_test.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -173,6 +174,14 @@ TEST(TransformError, SequenceFwd) { EXPECT_EQ(*one, 42); } +TEST(TransformError, LvalueConnectable) { + int n = 0; + sync_wait(repeat_effect_until( + let_error(just(), [](auto&&) { return just(); }), + [&n]() mutable noexcept { return n++ == 5; })); + EXPECT_EQ(n, 6); +} + #if !UNIFEX_NO_COROUTINES TEST(TransformError, WithTask) { auto value = let_error( diff --git a/test/let_value_test.cpp b/test/let_value_test.cpp index cbb7edddc..a428b0330 100644 --- a/test/let_value_test.cpp +++ b/test/let_value_test.cpp @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include #include + +#include +#include #include #include +#include #include #include #include @@ -281,3 +284,11 @@ TEST(Let, LetValueWithTraitlessPredecessor) { ASSERT_TRUE(ret); EXPECT_EQ(*ret, 42); } + +TEST(Let, LvalueConnectable) { + int n = 0; + sync_wait(repeat_effect_until( + let_value(let_done(just(), [] { return just(); }), [] { return just(); }), + [&n]() mutable noexcept { return n++ == 5; })); + EXPECT_EQ(n, 6); +} diff --git a/test/let_value_with_test.cpp b/test/let_value_with_test.cpp new file mode 100644 index 000000000..2bbf8992d --- /dev/null +++ b/test/let_value_with_test.cpp @@ -0,0 +1,51 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace unifex; + +namespace { +constexpr auto async = [](auto& context, auto&& func) { + return then( + schedule_after(context.get_scheduler(), std::chrono::milliseconds(10)), + (decltype(func))func); +}; +} + +TEST(LetValueWith, StatefulFactory) { + // Verifies the let_value_with operation state holds onto the + // Factory object until the operation is complete. + timed_single_thread_context context; + std::optional result = sync_wait( + let_value(just(), [&] { + return let_value_with([x = std::make_unique(10)]() -> int* { return x.get(); }, [&](int*& v) { + return async(context, [&v]() { return *v; }); + }); + }) + ); + ASSERT_TRUE(!!result); + EXPECT_EQ(*result, 10); +} + +TEST(LetValueWith, CallOperatorRvalueRefOverload) { + struct Factory { + int operator()() && { + return 10; + } + }; + std::optional result = sync_wait(let_value_with(Factory{}, [&](int& v) { + return just(v); + })); + ASSERT_TRUE(!!result); + EXPECT_EQ(*result, 10); +} diff --git a/test/static_thread_pool_test.cpp b/test/static_thread_pool_test.cpp index c35b3bf9f..ec408cb32 100644 --- a/test/static_thread_pool_test.cpp +++ b/test/static_thread_pool_test.cpp @@ -16,8 +16,12 @@ #include #include +#include +#include #include +#include #include +#include #include #include #include @@ -62,3 +66,36 @@ TEST(StaticThreadPool, Smoke) { EXPECT_EQ(x, 3); } + +TEST(StaticThreadPool, ScheduleCancelationThreadSafety) { + static_thread_pool tpContext; + auto sch = tpContext.get_scheduler(); + + unifex::sync_wait(unifex::repeat_effect_until( + unifex::let_done( + unifex::stop_when( + unifex::repeat_effect(unifex::schedule(sch)), + unifex::schedule(sch)), + [] { return unifex::just(); }), + [n=0]() mutable noexcept { return n++ == 1000; })); + + unifex::sync_wait(unifex::repeat_effect_until( + unifex::let_done( + unifex::let_error( + unifex::stop_when( + unifex::repeat_effect(unifex::schedule(sch)), + unifex::schedule(sch)), + [](auto&&) { return unifex::just(); }), + [] { return unifex::just(); }), + [n=0]() mutable noexcept { return n++ == 1000; })); + + unifex::sync_wait(unifex::repeat_effect_until( + unifex::let_error( + unifex::let_done( + unifex::stop_when( + unifex::repeat_effect(unifex::schedule(sch)), + unifex::schedule(sch)), + [] { return unifex::just(); }), + [](auto&&) { return unifex::just(); }), + [n=0]() mutable noexcept { return n++ == 1000; })); +}