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.