I have a third-party function with this signature:
std::vector<T> f(T t);
I also have an existing potentially infinite range (of the range-v3 sort) of T
named src
. I want to create a pipeline that maps f
to all elements of that range and flattens all the vectors into a single range with all their elements.
Instinctively, I would write the following.
auto rng = src | view::transform(f) | view::join;
However, this won't work, because we cannot create views of temporary containers.
How does range-v3 support such a range pipeline?
2 Answers
Answers 1
I suspect it just can't. None of the view
s have any machinery to store temporaries anywhere - that's explicitly against the concept of view from the docs:
A view is a lightweight wrapper that presents a view of an underlying sequence of elements in some custom way without mutating or copying it. Views are cheap to create and copy, and have non-owning reference semantics.
So in order for that join
to work and outlive the expression, something somewhere has to hold onto those temporaries. That something could be an action
. This would work (demo):
auto rng = src | view::transform(f) | action::join;
except obviously not for src
being infinite, and even for finite src
probably adds too much overhead for you to want to use anyway.
You would probably have to copy/rewrite view::join
to instead use some subtly modified version of view::all
(required here) that instead of requiring an lvalue container (and returning an iterator pair into it), allowed for an rvalue container that it would store internally (and returning an iterator pair into that stored version). But that's several hundred lines' worth of copying code, so seems pretty unsatisfactory, even if that works.
Answers 2
Edited
Apparently, the code below violates the rule that views cannot own data they refer to. (However, I don't know if it's strictly forbidden to write something like this.)
I use ranges::view_facade
to create a custom view. It holds a vector returned by f
(one at a time), changing it to a range. This makes it possible to use view::join
on a range of such ranges. Certainly, we can't have a random or bidirectional access to elements (but view::join
itself degrades a range to an Input range), nor can we assign to them.
I copied struct MyRange
from Eric Niebler's repository modifying it slightly.
#include <iostream> #include <range/v3/all.hpp> using namespace ranges; std::vector<int> f(int i) { return std::vector<int>(static_cast<size_t>(i), i); } template<typename T> struct MyRange: ranges::view_facade<MyRange<T>> { private: friend struct ranges::range_access; std::vector<T> data; struct cursor { private: typename std::vector<T>::const_iterator iter; public: cursor() = default; cursor(typename std::vector<T>::const_iterator it) : iter(it) {} T const & get() const { return *iter; } bool equal(cursor const &that) const { return iter == that.iter; } void next() { ++iter; } // Don't need those for an InputRange: // void prev() { --iter; } // std::ptrdiff_t distance_to(cursor const &that) const { return that.iter - iter; } // void advance(std::ptrdiff_t n) { iter += n; } }; cursor begin_cursor() const { return {data.begin()}; } cursor end_cursor() const { return {data.end()}; } public: MyRange() = default; explicit MyRange(const std::vector<T>& v) : data(v) {} explicit MyRange(std::vector<T>&& v) noexcept : data (std::move(v)) {} }; template <typename T> MyRange<T> to_MyRange(std::vector<T> && v) { return MyRange<T>(std::forward<std::vector<T>>(v)); } int main() { auto src = view::ints(1); // infinite list auto rng = src | view::transform(f) | view::transform(to_MyRange<int>) | view::join; for_each(rng | view::take(42), [](int i) { std::cout << i << ' '; }); } // Output: // 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9
Compiled with gcc 5.3.0.
0 comments:
Post a Comment