Home > Blockchain >  create a to_vector function that can conditionally const cast to elements of input range based on ou
create a to_vector function that can conditionally const cast to elements of input range based on ou

Time:12-05

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.

  • Related