Sunday, May 8, 2016

How do I write a range pipeline that uses temporary containers?

Leave a Comment

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 views 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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment