Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I not pass std::span<int> to a function template taking std::span<const T>?

I'm working with templates and type deduction in C++ and encountering a type deduction failure when using std::span but not with raw pointers. Below is a simplified version of my code:

#include <span>
#include <vector>

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

template <typename T>
void f2(const T* param)
{}

int main()
{
   std::vector<int> v{1,2,3};
   std::span<int> s{v};
   // Uncommenting this line causes a compilation error:
   // cannot deduce a type for 'T' that would make 'const T' equal 'int'
   // f1(s);
   int x = 10;
   int* px = &x;
   const int* z = px;
   f2(px); // Works fine
   f2(z);  // Works fine
}

When I uncomment the f1(s) call, I receive a compilation error stating that the compiler cannot deduce a type for T that would make const T equal int. However, similar template functions for pointers, like f2, compile without any issues when passing both int* and const int*.

Why does this error occur with std::span but not with pointers?

like image 489
passionateProgrammer Avatar asked Sep 01 '25 15:09

passionateProgrammer


2 Answers

T in std::span<const T> cannot be deduced from std::span<int> because int is not const. There does exist an implicit conversion from any std::span<U> to std::span<const U>, but such implicit conversions are not considered during template argument deduction. See also Why does the implicit type conversion not work in template deduction?

const T* param is a special case because there are dedicated rules for when deduction wouldn't give you an exact match for const T*, and const has to be added (via qualification conversion) ([temp.deduct.call] p4):

In general, the deduction process attempts to find template argument values that will make the deduced A identical to [the argument type] A (after the type A is transformed as described above). However, there are three cases that allow a difference:

  • [...]
  • The transformed A can be another pointer or pointer-to-member type that can be converted to the deduced A via a function pointer conversion and/or qualification conversion.
  • [...]

In your case, the argument type A is int*, which can be converted to const int* and thus match const T*.

Solution

In most cases, you should not write templates that take a std::span<T> due to these deduction issues. The outcome will always be somewhat confusing and unergonomic. Since f1 is a template anyway, you don't lose anything by writing:

template <std::ranges::contiguous_range R>
void f1(R&& range);

This would also let you pass in say, std::vector and std::string_view without converting them to a span first.

Note: In most cases, a contiguous range is overkill and you could get away with a random access range, or some weaker requirement.

like image 97
Jan Schultke Avatar answered Sep 04 '25 12:09

Jan Schultke


An std::span<T> is implicitly convertible to a std::span<const T>. So, if the T from your function was an int, your parameter of std::span<int> could be converted to the std::span<const int> the function would want. However, that doesn't mean the compiler will be able to figure that out in its template argument deduction process.

The compiler's template argument deduction process doesn't care what is convertible to what. It wants some argument for T such that std::span<const T> is the same as std::span<int>, and as it said in the error message, there wasn't one, because there isn't. If T is an int, as I say above, std::span<const T> would be a std::span<const int>, implicitly convertible from a std::span<int>. But it is still not exactly the same type, like the compiler wants, and so that option is excluded.

You can work around this two ways:

  • Explicitly specify the template arguments. So, f1<int>(s) instead of f1(s). This avoids any template argument deduction.
  • Alternatively, keep the template argument deduction, and make s a std::span<const int> instead of being a std::span<int> as it currently is. Now there does exist a type T such that std::span<const T> is a std::span<const int>, and the compiler will correctly identify and appropriate it.
like image 40
Brendan Lynn Avatar answered Sep 04 '25 12:09

Brendan Lynn