Search code examples
c++c++20ctad

Conversion of std::vector to std::span<T>


I'd like to generalize my code to take std::span rather than std::vector as parameter, but without losing the convenience of the auto-conversion you get when passing in a std::vector (see How can std::vector be converted to std::span?).

However, the functions in question are templated on the element type -- and I get a compile fail on use_span_t, below.

void use_span(std::span<const double>) {}

template <typename T>
void use_span_t(std::span<const T>)
{
}

const std::vector<double> v{1, 2, 3};
use_span(v); // ok
use_span_t(v); // candidate template ignored: could not match 'span' against 'vector' (clang16)
use_span_t<double>(v); // ok

What am I doing wrong, and is there a workaround which doesn't involve explicit types at the callsite (use_span_t<double>), or something like the following at the called site.

template <typename T>
void use_span_t(const std::vector<T>& v)
{
    use_span_t(v);
}


Solution

  • This function template is basically useless:

    template <typename T>
    void use_span_t(std::span<const T>);
    

    Because not only does it not accept a vector<double> or vector<double> const as you've seen, it doesn't even accept a span<double>. It accepts, precisely, a span<double const>. Which is highly limited. It'd be very nice if there were a way for this to work, but...

    Until then, if what you want to do is deduce T for any contiguous range and get a span over T const over that, you'll have to do it this way:

    template <typename T>
    void f(std::span<const T>);
    
    template <std::ranges::contiguous_range R>
        requires std::ranges::sized_range<R>
    void f(R&& r) {
        f(std::span<const std::ranges::range_value_t<R>>(r));
    }
    

    Any contiguous + sized is convertible to span, we just have to do it explicitly. range_value_t<R> gives us the value type (which is double for both vector<double> and const vector<double>), so we can use that to construct the correct span.

    If we called f with a span<T const> already, the first overload would be chosen. Anything else, including span<T> (for non-const T), would pick the second which would then forward to the first.

    I also wrote a post about coercing deep const-ness almost exactly two years ago now.