5

In the following C++20 code, passing a std::vector to a templated function with a std::span<T> parameter fails, because obviously the compiler cannot deduce the template parameter. I have tried this with GCC, Clang and MSVC; all fail.

Invoking like this works: f3(std::span(vi)) or f3(std::span(vp)).

I would like to know why this fails, because in my understanding, std::vector is a range, and std::span has a deduction guide for ranges.

#include <memory>
#include <vector>
#include <span>

void f1(std::span<int> s)
{
}

void f2(std::span<std::shared_ptr<int>> s)
{
}

template<typename T>
void f3(std::span<T> s)
{
}

int main(int argc, char* argv[])
{
    std::vector<int> vi;
    std::vector<std::shared_ptr<int>> vp;

    f1(vi);
    f2(vp);
    f3(vi); // ERROR: no matching function for call to 'f3'
    f3(vp); // ERROR: no matching function for call to 'f3'

    return 0;
}
Boann
  • 48,794
  • 16
  • 117
  • 146
FrankM
  • 1,007
  • 6
  • 15

3 Answers3

14

If a function argument participates in template argument deduction, no implicit conversions are allowed for that function argument.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Why does the std::span deduction guide for ranges have no effect? Shouldn't it allow such kind of conversions? – FrankM Sep 01 '23 at 08:07
  • @FrankM I don't fully understand the rationale behind this rule, but no, it doesn't seem to have an exception for deduction guides. – HolyBlackCat Sep 01 '23 at 08:08
  • 3
    @FrankM If you need a practical solution, perhaps just accept `T &&range`, then check with SFINAE if it's convertible to `std::span?>`, and for which element type. – HolyBlackCat Sep 01 '23 at 08:09
  • what if there is another `template void f3(foo)` and `foo` has also a deduction guide to be constructed from a vector? ... well ok then it could be an ambiguity error. Maybe just nobody proposed such exception yet. – 463035818_is_not_an_ai Sep 01 '23 at 08:20
  • @FrankM Class template argument deduction is not done on function parameters at all and the only cases where it is done use the syntax `std::span` without template argument list in the declaration (which therefore is not permitted for function parameters). – user17732522 Sep 01 '23 at 08:34
  • @HolyBlackCat, why not constraints rather than SFINAE? – Toby Speight Sep 01 '23 at 08:40
  • @TobySpeight I didn't mean to exclude `requires`, and I consider `requires`-expression a form of SFINAE (unsure if a `requires`-clause counts as SFINAE). – HolyBlackCat Sep 01 '23 at 08:48
  • Try `f3(std::span{vi});` – Red.Wave Sep 01 '23 at 10:09
2

Unfortunately, what you're trying to do is impossible. In the call ....

template<typename T>
void f3(std::span<T> s) { /* ... */ }
// [...]

std::vector<int> vi;
f3(vi);

... the std::vector vi is not a std::span, so the template parameter T cannot be deduced from it. Implicit conversions are not considered by function template argument deduction.

You have pointed out that there is a deduction guide, but that feature cannot be used here:

Deduction guides are used when a template-name appears as a type specifier for a deduced class type.

- [temp.deduct.guide]

This means that the std::span deduction guide ...

template< class R >
span( R&& ) -> span<std::remove_reference_t<std::ranges::range_reference_t<R>>>;

... could be used in a scenario like:

f3(std::span(vi));
// or
std::span s = vi;

In either case, this forms a std::span<int>.

Solution

Overall, this is a well-known and quite annoying limitation of std::span. If you want a "generic span", the idiomatic solution is to accept a constrained range:

#include <ranges>

template <std::ranges::contiguous_range R>
  requires /* TODO: additional constraints for the range value type? */
void foo(R&& r) {
    // ...
}

Since you're writing a function template anyway, making one which accepts any std::ranges::contiguous_range is just as good as writing one which only accepts std::span.

Most of the time, you could use a more relaxed range requirement though, such as std::ranges::forward_range.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
1

I would like to know why this fails, because in my understanding, std::vector is a range, and std::span has a deduction guide for ranges.

This answer explains why this doesn't work. If you still want f3(vi) to work, you can do so by checking if the type R enables the deduction guide of the std::span:

#include <span>

template<class R>
  requires requires(R&& r) { std::span(std::forward<R>(r)); }
void use_as_span(R&& r) {
  std::span sp(std::forward<R>(r));
  // use span here
}

Demo

康桓瑋
  • 33,481
  • 5
  • 40
  • 90