c++templatesc++20std-span

Instantiating a function template with a std::span<T> parameter


I'm encountering an issue where the compiler seems unable to implicitly deduce the template argument T when I try to call largest() with a std::vector<T>. Explicitly specifying T (e.g., largest<std::string>(myVector)) works, but I'm seeking a deeper understanding of why this is necessary.

std::string largest(std::span< std::string > elements);
template <typename T> std::optional<T> largest(std::span<T> elements);

int main(int argc, char* argv[]) {

    std::vector< std::string > elements { "A", "B", "C", "D" };
    std::cout << largest(elements) << std::endl;

    std::vector<int> integers { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
    // std::cout << largest(integers) << std::endl; COMPILER CAN'T RESOLVE OVERLOAD

    // correct version
    std::optional<int> maximum { largest<int>(integers) };
    if(maximum.has_value())
        std::cout << maximum.value() << std::endl;

    return 0;
}

std::string largest(std::span< std::string > elements) {

    std::string maximum {};
    for(const auto& element : elements) {

        if(auto ordering { element <=> maximum }; ordering == std::strong_ordering::greater)
            maximum = element;

    }

    return maximum;

}

template <typename T> std::optional<T> largest(std::span<T> elements) {

    if( elements.empty() ) return std::nullopt;
    T maximum {};

    for(const auto& element : elements) {

        if(auto ordering { element <=> maximum }; ordering == std::strong_ordering::greater)
            maximum = element;

    }

    return maximum;
}

Solution

  • the compiler seems unable to implicitly deduce the template argument T when I try to call largest() with a std::vector<T>

    Yes, for it to be possible to deduce T, you'd have to supply a std::span over "something". The match must be exact for deduction to work and a vector<T> and a span<T> are not the same type and it doesn't help that a span<T> has a converting constructor that lets it be created from a vector<T>.

    You could however make it simpler by just accepting any forward range. To simplify it further, you could also use a standard algorithm to find the element with the maximum value.

    Example:

    #include <algorithm>
    #include <iterator>
    #include <optional>
    #include <ranges>
    
    auto largest(std::ranges::forward_range auto&& elements) {
        // get an iterator to the maximum element:
        auto maxit = std::ranges::max_element(elements);
    
        // create an empty optional with the correct value type:
        std::optional<std::iter_value_t<decltype(maxit)>> maximum;
    
        // if the range was not empty, assign the found value:
        if (maxit != std::ranges::end(elements)) maximum = *maxit;
    
        return maximum;
    }