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;
}
If a function argument participates in template argument deduction, no implicit conversions are allowed for that function argument.
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>.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With