Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace C-style cast with move() in let* algos #552

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion include/unifex/let_done.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class _op<Source, Done, Receiver>::type {
, receiver_((Receiver2&&)dest)
{
unifex::activate_union_member_with(sourceOp_, [&] {
return unifex::connect((Source&&)source, source_receiver{this});
return unifex::connect(std::move(source), source_receiver{this});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow this has broken the MSVC build. I wonder if Source is sometimes an lvalue reference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup; line 324 instantiates an operation with an lvalue ref Source if the Sender argument is an lvalue ref. This is a std::forward.

However, most of the operation state types that I've written myself have a templated constructor that takes a forwarding reference to the Sender, rather than templating the whole type on a forwarding reference. It seems like there'd be less duplicated code in the resulting binary if we could follow that pattern.

});
startedOp_ = 0 + 1;
}
Expand Down
2 changes: 1 addition & 1 deletion include/unifex/let_error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class _op<Source, Func, Receiver>::type final {
: func_((Func2 &&) func)
, receiver_((Receiver2 &&) dest) {
unifex::activate_union_member_with(sourceOp_, [&] {
return unifex::connect((Source &&) source, source_receiver{this});
return unifex::connect(std::move(source), source_receiver{this});
});
}

Expand Down
7 changes: 4 additions & 3 deletions include/unifex/let_value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>

#include <unifex/detail/prologue.hpp>

Expand Down Expand Up @@ -85,7 +86,7 @@ struct _successor_receiver<Operation, Values...>::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:
Expand Down Expand Up @@ -178,7 +179,7 @@ struct _predecessor_receiver<Operation>::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)
Expand Down Expand Up @@ -236,7 +237,7 @@ struct _op<Predecessor, SuccessorFactory, Receiver>::type {
receiver_((Receiver2 &&) receiver) {
unifex::activate_union_member_with(predOp_, [&] {
return unifex::connect(
(Predecessor &&) pred, predecessor_receiver<operation>{*this});
std::move(pred), predecessor_receiver<operation>{*this});
});
}

Expand Down
8 changes: 5 additions & 3 deletions include/unifex/let_value_with.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <unifex/execution_policy.hpp>
#include <unifex/type_list.hpp>

#include <utility>

#include <unifex/detail/prologue.hpp>

namespace unifex {
Expand Down Expand Up @@ -100,12 +102,12 @@ template<typename StateFactory, typename SuccessorFactory, typename Receiver>
struct _operation<StateFactory, SuccessorFactory, Receiver>::type {
template <typename StateFactory2, typename SuccessorFactory2, typename Receiver2>
type(StateFactory2&& stateFactory, SuccessorFactory2&& func, Receiver2&& r) :
stateFactory_(static_cast<StateFactory2&&>(stateFactory)),
stateFactory_(std::forward<StateFactory2>(stateFactory)),
func_(static_cast<SuccessorFactory2&&>(func)),
state_(static_cast<StateFactory&&>(stateFactory_)()),
state_(std::move(stateFactory_)()),
innerOp_(
unifex::connect(
static_cast<SuccessorFactory&&>(func_)(state_),
std::move(func_)(state_),
static_cast<Receiver2&&>(r))) {
}

Expand Down
4 changes: 3 additions & 1 deletion include/unifex/let_value_with_stop_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include <unifex/execution_policy.hpp>
#include <unifex/type_list.hpp>

#include <utility>

#include <unifex/detail/prologue.hpp>

namespace unifex {
Expand Down Expand Up @@ -165,7 +167,7 @@ struct _stop_source_operation<SuccessorFactory, Receiver>::type {
nothrow_connectable) {
return unifex::connect(
static_cast<SuccessorFactory&&>(func)(stopSource_),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a move, too, which might mean it should be taken by rvalue reference.

Suggested change
static_cast<SuccessorFactory&&>(func)(stopSource_),
std::move(func)(stopSource_),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SuccessorFactory&& also seems like a "saved for later forwarding reference," although it's used differently than in the other algos. Do we need func_ to remain alive for the duration of the operation? In the operation constructor, we currently have (annotated with // comment)

template <typename SuccessorFactory2, typename Receiver2>
  type(SuccessorFactory2&& func, Receiver2&& r) noexcept(
      std::is_nothrow_constructible_v<SuccessorFactory, SuccessorFactory2>&&
          std::is_nothrow_constructible_v<Receiver, Receiver2>&& noexcept(
              connect_inner_op(func, (Receiver2 &&) r))) // borrow func, whether it's an lvalue ref or rvalue erf
    : func_{(SuccessorFactory2 &&) func} // forward func into func_
    , receiverToken_(get_stop_token(r))
    , innerOp_(connect_inner_op(func_, (Receiver2 &&) r)) {} // borrow func_, but don't move so that func_ remains alive

SuccessorFactory& in connect_inner_op seems correct so that it borrows but doesn't possibly move from.

receiver_t{*this, static_cast<Receiver&&>(r)});
receiver_t{*this, std::move(r)});
}

public:
Expand Down
2 changes: 2 additions & 0 deletions include/unifex/let_value_with_stop_token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include <unifex/sender_concepts.hpp>
#include <unifex/type_list.hpp>

#include <utility>

#include <unifex/detail/prologue.hpp>

namespace unifex {
Expand Down
51 changes: 51 additions & 0 deletions test/let_value_with_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <unifex/let_value_with.hpp>

#include <unifex/just.hpp>
#include <unifex/let_value_with.hpp>
#include <unifex/sync_wait.hpp>
#include <unifex/then.hpp>
#include <unifex/timed_single_thread_context.hpp>
#include <chrono>
#include <iostream>
#include <optional>
#include <variant>

#include <gtest/gtest.h>

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<int> result = sync_wait(
let_value(just(), [&] {
return let_value_with([x = std::make_unique<int>(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<int> result = sync_wait(let_value_with(Factory{}, [&](int& v) {
return just(v);
}));
ASSERT_TRUE(!!result);
EXPECT_EQ(*result, 10);
}