c++inheritancevectorreferencestd-span

How can I reference a vector<Derived> with a span<Base>


So my question is simple. I have std::vector<Derived> and it’s a natural desire to have a std::span<Base> to it. Derive inherits from Base:

#include <span>
#include <vector>

class Base {};
class Derived : public Base {};

int main 
{
    std::vector<Derived> vec {Derived{}, Derived{}};
    // std::span<Base> span = ???;
    return 0; 
}

I guess this is impossible. Is there any common workaround? If so, are there any limitations of that?

I’m stuck with that. I see no solution


Solution

  • Before using C++20 <ranges>, I like to do some boilerplate code:

    #include <ranges>
    #include <concepts>
    #includr <type_traits>
    
    template<typename to>
    constexpr auto object_static_cast = 
        []<std::convertible_to<to> from>(from&& src)
        -> decltype(auto)
        { return static_cast<to>(std::forward<from>(src); };
    
    template<typename to>
    constexpr auto static_element_cast =
        std::views::transform(object_static_cast);
    
    

    Now assume that we have an algorithm for randomly accessible containers with available element count:

    void my_algo(std::ranges::view auto rng)
    requires std::ranges::random_access_range<decltype(rng)>
         and std::ranges::sized_range<decltype(rng)>
         and std::same_as<Base
           , std::ranges:: range_reference_t<decltype(rng)>{
        /* Whatever */
    };
    

    This algorithm can accept a std::span which is a ranges::contiguous_range; that means std::span is more than qualified to be used as an input to my_algo:

    std::vector<Base> my_base_vec;
    
    my_algo(span(my_base_vec));
    my_algo(my_base_vec | std::views::all);
    

    I deliberately forced users of my_algo to explicitly convert the parameter to a ranges::view instead of accepting a container object by reference. The explicitness of this conversion IMO increases readability. In order to call the algorithm on a range of derived objects I can perform a simple transformation:

    std::vector<Derived> my_derived_vec;
    my_algo(my_derived_vec | static_element_cast<Base&>);
    

    Alternatively, I could change the last constraint on my_algo to

    std::derived_from
       < std::ranges::range_value_t<decltype(rng)>
       , Base >
    

    . Thus, avoiding the need to transform the input:

    my_algo(my_derived_vec | std::views::all);
    my_algo(span(my_derived_vec));
    

    Notice that for ranges::sized_range classes, views::all is practically equivalent to span.

    In general, references to objects of ranges::contiguous_range classes cannot be converted by-reference to any other type. The elements-wise conversion however, can be fullfilled via a views::transform which is used as the implementation of static_element_cast in my first snippet. Then the algorithm needs to be generic. Another option would be to use std::vector<smart_ptr<Base>> as the desired container - with smart_ptr being std::unique_ptr or std::shared_ptr, etc. That would avoid defining a generic algorithm in header, but at the cost of extra memory use and runtime overhead - due to extra runtime indirections leading to increased number of cach-miss.