Now that the C++20 ranges implementation is actually here and released under GCC 10.2, I would like to know how to convert a ranges view back to an actual container, like a vector. I've found this question ( Range view to std::vector ) that asked the same thing for the pre-release version but I would like to know if since its been released, have there been any new methods made to convert from a view to a container? Or is that single answer on that question still the best solution?
ranges::range. (C++20) specifies that a type is a range, that is, it provides a begin iterator and an end sentinel. (concept)
Range - Ranges are an abstraction that allows a C++ program to operate on elements of data structures uniformly. On minimum a range contains defines begin() and end() to elements. There are several different types of ranges: containers, views, sized ranges, Container - It's a range that owns the elements.
Easiest thing to do would be to use range-v3, which has a conversion operator exactly for this. From the examples:
using namespace ranges;
auto vi =
views::for_each(views::ints(1, 10), [](int i) {
return yield_from(views::repeat_n(i, i));
})
| to<std::vector>();
// vi == {1,2,2,3,3,3,4,4,4,4,5,5,5,5,5,...}
Otherwise, the answer in the linked question isn't entirely accurate since a range may not have the same iterator and sentinel types, and the answer requires it. So we can do a little bit better:
template <std::ranges::range R>
auto to_vector(R&& r) {
std::vector<std::ranges::range_value_t<R>> v;
// if we can get a size, reserve that much
if constexpr (requires { std::ranges::size(r); }) {
v.reserve(std::ranges::size(r));
}
// push all the elements
for (auto&& e : r) {
v.push_back(static_cast<decltype(e)&&>(e));
}
return v;
}
A shorter version of the above, which doesn't necessarily reserve up front in all the same places, addresses the mixed-sentinel-type issue by using views::common
:
template <std::ranges::range R>
auto to_vector(R&& r) {
auto r_common = r | std::views::common;
return std::vector(r_common.begin(), r_common.end());
}
The canonical example of missed reserve here would be invoking to_vector()
with a std::list<T>
- which has an O(1) size()
available, which could be used to reserve, but we lose that once we go into the iterators.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With