Reputation: 1468
In the proposed C++20 (The One) Ranges TS, what is the proposed method for converting the view into a std::vector?
The following code does not compile:
int
main() {
std::vector<float> values = {1.0, 2.0, 3.0, 4.0, 5.2, 6.0, 7.0, 8.0, 9.0};
//fmt::print("{}\n", std::experimental::ranges::views::filter(values, [] (float v) { return v < 5.f; }));
std::vector<float> foo = vw::filter(values, [] (float v) { return v < 5.f; });
fmt::print("{}\n", foo);
}
with the error
../src/view.cpp:19:40: error: conversion from ‘std::experimental::ranges::v1::filter_view<std::experimental::ranges::v1::ref_view<std::vector<float> >, main()::<lambda(float)> >’ to non-scalar type ‘std::vector<float>’ requested
std::vector<float> foo = vw::filter(values, [] (float v) { return v < 5.f; });
(the commented line will also not compile due to some CV constraints).
So how do I do anything with a view except for using a range-based for loop?
Also some bonus questions:
Upvotes: 29
Views: 21127
Reputation: 1564
The C++20 method to convert a view to a std::vector
(or indeed any other container) is to pass the range's begin
and end
members to the vector
constructor that accepts 2 iterators (and an optional allocator).
I was also looking for an answer to this question. What I really wanted is an overload of the constructor of std::vector
accepting a range. Approximately:
template <std::ranges::input_range R>
vector(R&& r) : vector(r.begin(), r.end()) {
}
but that isn't in C++20.
First, I implemented this:
namespace rng = std::ranges;
template <rng::range R>
constexpr auto to_vector(R&& r) {
using elem_t = std::decay_t<rng::range_value_t<R>>;
return std::vector<elem_t>{r.begin(), r.end()};
}
which works, but isn't very "rangy": https://godbolt.org/z/f2xAcd
I then did it a bit better:
namespace detail {
// Type acts as a tag to find the correct operator| overload
template <typename C>
struct to_helper {
};
// This actually does the work
template <typename Container, rng::range R>
requires std::convertible_to<rng::range_value_t<R>, typename Container::value_type>
Container operator|(R&& r, to_helper<Container>) {
return Container{r.begin(), r.end()};
}
}
// Couldn't find an concept for container, however a
// container is a range, but not a view.
template <rng::range Container>
requires (!rng::view<Container>)
auto to() {
return detail::to_helper<Container>{};
}
https://godbolt.org/z/G8cEGqeq6
No doubt one can do better for sized_range
s and containers like std::vector
that have a reserve
member function.
C++23 has added a to<Container>
function - https://en.cppreference.com/w/cpp/ranges/to. You can use a template (of one type parameter) or a concrete type as the parameter. E.g.: to::<std::vector<int>>
vs to::<std::vector>
. In the latter case the type used to instantiate the container is deduced so attention will have to be paid that it turns out as desired.
Note that the standard requires that for containers that have reserve
and append (i.e. push_back
/emplace_back
/etc) functionality the implementation must do the efficient thing of calling reserve first.
Upvotes: 33
Reputation: 38112
IMO problem here is incorrect use of fmt
library. In presented example you do not need to do conversion to the std::vector
you can feed range (view here) into fmt
function:
int main()
{
std::vector<float> values = { 1.0, 2.0, 3.0, 4.0, 5.2, 6.0, 7.0, 8.0, 9.0 };
auto foo = std::views::filter(values, [](float v) { return v < 5.f; });
fmt::print("[{}]\n", fmt::join(foo, ", "));
}
So no need to use C++23, just leverage functionality of fmt
library.
https://godbolt.org/z/v9qnEnY7o
Upvotes: 1
Reputation: 3637
C++23 finally provided an official solution: std::ranges::to
.
// pipeline style
auto vec = view | std::ranges::to<std::vector>();
// function style
auto vec = std::ranges::to<std::vector>(view);
Presently, compiler support is lacking. GCC 14, to be released in 2024, is expected to include its support. This feature is already working in GCC 14's development version.
Upvotes: 24