-
Notifications
You must be signed in to change notification settings - Fork 79
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
added pipeline reduction by value #62
base: master
Are you sure you want to change the base?
Conversation
This is an interesting implementation of your idea. For the moment I understand this is a POC. |
clang fails because It is a POC because it is minimal (I haven't implemented the other requested reduce operations), and because the API for I think that the discussion can progress further by having a working implementation at hand. |
I was working on something similar (https://github.com/fried-water/pipes/commit/ce3181e1e84b02c605b06404a8d75265cf651d28). Seems like we arrived at the same basic idea. I spent a bit more time trying to minimize the moves and copies that occurred throughout. Additionally it seemed nice to have an extra overload to use the accumulator as an lvalue like so.
|
It is indeed very close. I can see that you have had the same problem as me with Here are my comments on the few differences we have :
About the additional moves, it is a separate topic. But I don't expect it to change anything in almost all use cases due to copy elision and the fact that most (all ?) rvalue pipelines don't have a useful move constructor. It doesn't seem to hurt either. |
I would expect the move version to only be overloaded for rvalues, as moving an lvalue would be very unexpected as a user. At that point the only options with lvalues is either not compile or copy. Seems like there is no downside to offer the option.
Yeah I need a different name.
Yeah I hadn't gotten around to properly sfinaeing based on insert().
Yeah that's a good point. Overall I think its important to note that accumulator pipelines are no longer output iterators. Accumulators that return move-only values will no longer be copyable, and passing accumulators with types that aren't cheap to copy (eg. containers) to functions that expect cheap to copy iterators seems like a bad idea. That's why the |
Ok, the only point where our views differ is the move behaviour of returning pipelines. You do
whereas I would do
because I view accumulator as a pipe version of a pure (stateless) function, whereas you view it as a stateful object. In my view, I don't move from an lvalue, I return from a function. They are actually two different end pipes (we could have both). |
Right so in the more common rvalue case, it won't make a difference.
I can see your point about the accumulator appearing stateless from the users perspective. But internally the accumulator has to store some state, hiding that would result in surprises when trying to use a const accumulator. It either won't compile or the internal state needs to marked mutable, which might lead to surprises if attempted to use in a multithreaded context.
|
What you say is true. However, as a user, I don't see it as a big issue to do the following without the const. const std::vector<int> v = {1, 2, 3};
auto functional_pipeline = pipes::transform(f) >>= pipes::to_<std::vector<int>>();
REQUIRE((v >>= functional_pipeline ) == v); Like that, I don't hide that there is a state internally, and there is no surprise. But I can use it as a function. If you want the const, you can use a lambda, which actually uses to the rvalue case. const std::vector<int> v = {1, 2, 3};
auto const functional_pipeline = [](){retrun pipes::transform(f) >>= pipes::to_<std::vector<int>>()};
// This would be assumed to work if accumulator is indeed stateless
REQUIRE((v >>= functional_pipeline() ) == v); This lead me to a new idea : making const std::vector<int> v = {1, 2, 3};
REQUIRE((v >>= pipes::push_back(std::vector<int>{}) ) == v); // same as using pipes::to_<std::vector<int>>
auto const functional_pipeline = [f](std::vector<int>&& container){
static auto const fixed_pipeline=pipes::transform(f);
return fixed_pipeline >>= pipes::push_back(std::move(container));
};
REQUIRE((v >>= functional_pipeline({}) ) == v);
// same pipeline can be used to accumulate results like your accumulate
const std::vector<int> v2 = {4, 5, 6};
std::vector<int> results = {};
v >>= functional_pipeline(results);
v2 >>= functional_pipeline(results);
auto stateful_pipeline = pipes::push_back(std::vector<int>{}); // forbidden, would cause a dangling reference In general, you can always explicitly accumulate to the same result like above. |
Fundamentally my problem is that this is not true in the current implementation. Otherwise I wonder if it could be implemented in a stateless way somehow by having the accumulated value on the stack in the |
…f `std::ignore`
According to issue #33, here is a proof of concept of pipeline reduction by value.
Only
pipes::fork
and the newpipes::to_<Container>
are currently returning pipelines, but it is very easy to implement new ones or adapt existing ones.For now, I have done the simplest implementation of
pipes::fork
, which returns astd::tuple
in which none values must bestd::ignore
d. As discussed, this interface is not the only one possible, and I can adapt the code if another interface is chosen.