[discussion] Implementing `drop_last` for pipes
kunaltyagi opened this issue · 2 comments
If a pipe is allowed to store the values locally in a circular queue, it can accumulate the first N values in it, and then release the rest.
Assuming an interface like Boost.CircularBuffer for the circular queue
class drop_last
{
circular_buffer<std::any> queue_;
public:
template <typename... Values, typename TailPipeline>
void onReceive(Values&&... values, TailPipeline&& tailPipeline)
{
using DataStore = std::tuple<Values...>;
if(queue_.full()) {
send(SOMEHOW_FORWARD_OR_MOVE(std::any_cast<DataStore>(queue_.front()), FWD(tailPipeline));
}
queue_.push_back(std::make_any<DataStore>(values...));
}
explicit drop_last(std::size_t nbToDrop) queue_(nbToDrop) {}
};
Can it be made more efficient?
- If the size is 1, we can use
std::exchange
to efficiently move the values. If the size is constant, we could use a circular buffer on stack (eg: with a backend onstd::array
instead ofstd::vector
) - If the circular buffer implementation supports a
std::exchange
like operation,queue_.front()
andqueue_.push_back
could be made one single operation - If the
drop_last
was templated instead ofonReceive
, there would be no need of usingstd::any
Big issue: SOMEHOW_FORWARD_OR_MOVE
Thanks for the idea.
Doesn't this implementation assume that all the (tuples of) values coming successively to the pipe are of the same type?
Doesn't this implementation assume that all the (tuples of) values coming successively to the pipe are of the same type?
Yes, but I feel that this is a "reasonably" safe (and in case of input coming from containers, guaranteed) assumption (what's a few throws among friends)
Perhaps, this limitation can be made explicit by templating the class itself (and thus removing the need for std::any
)
Or else, it'd need tuple + lambda shenanigans to do it:
// the template part
template <class T, class... V>
struct exchange_result {
T old;
std::tuple<V...> tuple;
};
template <class Value, class V0, class...V>
auto tuple_exchange(std::tuple<V0, V...>&& existing, Value&& in) {
auto a = std::move(std::get<0>(existing));
return exchange_result{a,
std::make_tuple<V..., Value>(std::move(std::get<V>(existing))..., in)};
}
I am not sure if what I wish to do with lambda is legal (or possible), but essentially, I wanted to use onReceive to store a lambda in a std::function. The lambda acts the storage for the tuple and the queue logic, while returning a new lambda for the onReceive function to store (and delete the original).
EDIT:
If we force the interface to have a constant type (or std::any) we can have really nice interface