'c++20 ranges view to vector
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?
Solution 1:[1]
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.
Solution 2:[2]
Using Barry's answer and creating an adapter:
/**
* \brief Creates a to_vector_closure for operator()
*/
struct to_vector_adapter
{
struct closure
{
/**
* \brief Gets a vector of a given range.
* \tparam R type of range that gets converted to a vector.
* \param r range that gets converted to a vector.
* \return vector from the given range.
*/
template<std::ranges::range R>
constexpr auto operator()(R&& r) const
{
auto r_common = r | std::views::common;
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));
}
v.insert(v.begin(), r_common.begin(), r_common.end());
return v;
}
};
/**
* \brief Gets a closure to convert the range to a vector.
* \return A to_vector_closure that will convert the range to a vector.
*/
constexpr auto operator()() const -> closure
{
return closure{};
}
template<std::ranges::range R>
constexpr auto operator()(R&& r)
{
return closure{}(r);
}
};
inline to_vector_adapter to_vector;
/**
* \brief A range pipe that results in a vector.
* \tparam R type of range that gets converted to a vector.
* \param r range that gets converted to a vector.
* \param a used to create the vector.
* \return a vector from the given range.
*/
template<std::ranges::range R>
constexpr auto operator|(R&& r, aplasp::planning::to_vector_adapter::closure const& a)
{
return a(std::forward<R>(r));
}
With this you can do something like (where numbers
is a range of int
):
std::vector foo
{
numbers
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; })
| to_vector();
};
or
std::vector foo
{
to_vector(
numbers
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; }))
};
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Barry |
Solution 2 |