How to Write a Range Pipeline That Uses Temporary Containers

Why aren't temporary container objects pipeable in range-v3?

Short answer would be because they are lazy and | does not transfer ownership.

I would expect the lifetime of the temporary to be extended to the lifetime of the whole pipeline expression so I don't understand what the problem is.

Yes, that is exactly what would happen, but nothing more. Meaning that as soon as the code hits ;, some_ints() dies and num_strings now contains "dangling" range. So a choice was made to forbid this example to compile.

With Range v3 ranges, how to combine views and actions into a single pipeline?

Yes you can. You need to use a conversion to materialise the view into an actual container to perform actions on it. I found a new piece of code in the range-v3 master branch introducing range::v3::to<Container> to perform such conversions.

git blame suggests that Eric started working on it this year (2019) and it is not really documented yet. However, I find range-v3/test pretty good learning material on how the library is used :)

I doubt that it is available in the VS2015 branch. However, Visual 2017 is already able to take the master branch of the library.

#include <string>
#include <iostream>
#include <cctype>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/action/sort.hpp>
#include <range/v3/action/unique.hpp>
#include <range/v3/range/conversion.hpp>

int main() {
using namespace ranges::v3;
std::string input = " 1a2a3Z4b5Z6cz ";
std::string result = input
| view::filter(::isalpha)
| view::transform(::tolower)
| to<std::string>
| action::sort
| action::unique;
std::cout << result << std::endl;
return 0;
}

Outputs:

abcz

which I believe is what you expect

How does range-v3's `partial_sum` not contradict non-owning reference semantics?

What makes a view a view is that it doesn't take or require ownership of, copy, or modify any of the elements of the input range. But a view isn't required to have no state whatsoever. Even take() or filter() have some state (a counter and a predicate, respectively).

In this specific case, partial_sum doesn't have to own any of the elements of the input range. That's the input range's job. It also doesn't need to copy or modify them. It merely needs to keep track of its own state - the running sum (an optional<glorified_int>) and the binary function doing the summing (a plus). It owns one of its own objects, but that object exists outside of the input range entirely. That still makes it a view, just a stateful one.

You write:

The view is as expensive to copy as the accumulation object.

This is true. But that's also true of many views. transform() is as expensive to copy as the function we're using to transform the view, maybe you have an enormous stateful, expensive, memory-allocating monstrosity.

When Eric writes about cheap to create and copy, I believe he means in the context of creating and copying the entire input range to produce a new range. While partial_sum() needs to keep the running sum, which in your case isn't cheap since that element needs allocation, that's still far cheaper than writing an action-based partial_sum:

// cheap version
for(const auto &ps: vi | view::partial_sum()) { ... }

// expensive version
std::vector<glorified_int> partial_sums;
if (!vi.empty()) {
auto it = vi.begin();
partial_sums.emplace_back(*it++);
for (; it != vi.end(); ++it) {
partial_sums.emplace_back(*it + partial_sums.back());
}
}
for (const auto &ps : partial_sums) { ... }

We obviously don't need the entire partial_sums vector to do what we want (if we did need it, well, no way around that). The view offers us a cheap way to, well, view the partial sums.



Related Topics



Leave a reply



Submit