I would like to create a to_vector function that works with input vieweable_ranges. I can easily get this to work if input view and output vector have exactly the same type, but cannot get it to work if the output requires a const cast on the elements of the input range. In my case, the input ranges have non-const pointers, but the output may specify const pointers. I want to use it something like this:
auto a = to_vector( view ); // returns a vector<obj *>. This case is easy
vector<const obj *> b = to_vector( view ); // returns a vector of const pointers. This gives error shown at the end
The standard library has no problem casting pointers when making the vector
vector<const obj *> c( view.begin(), view.end() );
But I can't create a templated function to make it work. I've tried many variations, but I think my closest idea is something like this:
#include <ranges>
#include <vector>
using namespace std::ranges;
template <range Range, typename T = range_value_t<Range>> // default element type, T, based on input range
inline std::vector<T> to_vector(Range&& r) { // idea: user specifies T. (doesn't work)
std::vector<T> v;
if constexpr (std::ranges::sized_range<T>) {
v.reserve(std::ranges::size(r));
}
std::copy(std::ranges::begin(r), std::ranges::end(r), std::back_inserter(v));
return v;
}
The to_vector compiles, but I get an error when I try to require a vector output with an error something like this:
cannot convert std::vector<Obj *,std::allocator<Obj *>>' to 'std::vector<const Obj *,std::allocator<const Obj *>>
CodePudding user response:
This is because your function creates a return value of type std::vector<Obj*>
, which is indeed a different type to std::vector<const Obj*>
; and so unless std::vector
provided an overloaded constructor which accepted a non-const version of itself then this conversion is impossible. The return value is not deduced by the value you assign the result of the function to but by the T
template parameter, so this will work if you explicitly call the function with T=const Obj*
You would need to explicitly overload the function to construct a vector of const objects, either by passing a dummy tag parameter, an extra template parameter or a to_const_vector
function.
CodePudding user response:
As suggested by Dominic Price I split the template function into two separate functions. The first works with all objects but may have a slightly more cumbersome calling convention. The second works with const pointers, and where it is difficult to get the type of a std::views.
template <range R, typename T = range_value_t<R> >
inline auto to_vector(R &&r) {
return std::vector<T>(r.begin(), r.end()); // treats sized ranges correctly
}
template <range R, typename T>
inline auto to_vector(R &&r, T &&) {
return std::vector<T>(r.begin(), r.end()); // treats sized ranges correctly
}
This allows the following syntax from the caller something like this:
struct Unit {};
using UnitPtr = Unit const *;
using Units = vector<UnitPtr>;
unordered_set<Unit *> clusters; // clusters could even be a rvalue view
auto u = to_vector(clusters); // will return vector<Unit *>
Units v = to_vector<Clusters, UnitPtr>(clusters); // need typenames...
Units w = to_vector(clusters, UnitPtr()); // ... or this for convertable
This may have the downside that the argument T is may be constructed. My use is only for pointers, so not an issue.