C 20 introduced std::span
, which is a view-like object that can takes in a continuous sequence, such as C-style array, std::array
, and std::vector
. A common problem with C-style array is it will decay to a pointer when passing to a function. Such problem can be solved by using std::span
:
size_t size(std::span<int> s)
{
return s.size();
}
int main()
{
std::array arr = {1,2,3,4,5};
std::vector vec = {1,2,3,4,5};
auto il = {1,2,3,4,5};
int c_arr[] = {1,2,3,4,5};
std::cout << size(arr) << size(vec) << size(il) << size(c_arr);
}
This would print 5555
, as expected. However, size
probably shouldn't take in only containers of int
, instead it should take in containers of any types. However, changing the size
to a templated function, that takes in a std::span<T>
, it can no longer substitute the C-style array successfully, while the others can:
template<typename T>
size_t size(std::span<T> s)
{
return s.size();
}
int main()
{
std::array arr = {1,2,3,4,5};
std::vector vec = {1,2,3,4,5};
auto il = {1,2,3,4,5};
int c_arr[] = {1,2,3,4,5};
std::cout << size(arr) << size(vec) << size(il) << size(c_arr);
^^^^^^^^^^^
// error: no matching function for call to 'size(int [5])'
// note: template argument deduction/substitution failed:
// note: mismatched types 'std::span<_Type, 18446744073709551615>' and 'int*'
}
Is this the correct behavior? If so, is there a way to accept c-style array with span<T>
?
CodePudding user response:
The question is not why this fails for int[]
, but why it works for all the other types! Unfortunately, you have fallen prey to ADL which is actually calling std::size
instead of the size
function you have written. This is because all overloads of your function fail, and so it looks in the namespace of the first argument for a matching function, where it finds std::size
. Rerun your program with the function renamed to something else:
template<typename T>
size_t my_size(std::span<T> s)
{
return s.size();
}
and on gcc 12 i get
prog.cc:18:25: error: no matching function for call to 'my_size(std::array<int, 5>&)'
18 | std::cout << my_size(arr) << my_size(vec) << my_size(il) << my_size(c_arr);
| ~~~~~~~^~~~~
prog.cc:7:8: note: candidate: 'template<class T> size_t my_size(std::span<_Type, 18446744073709551615>)'
7 | size_t my_size(std::span<T> s)
| ^~~~~~~
prog.cc:7:8: note: template argument deduction/substitution failed:
prog.cc:18:25: note: 'std::array<int, 5>' is not derived from 'std::span<_Type, 18446744073709551615>'
18 | std::cout << my_size(arr) << my_size(vec) << my_size(il) << my_size(c_arr);
| ~~~~~~~^~~~~
plus similar errors for all the other types. If your question is why is this failing; then the short answer is that template type deduction is far more strict than regular type deduction and so unless you give it a exact match (more or less) it will fail. For a more detailed explanation, read a similar question such as this one which deals with something similar
CodePudding user response:
Well, as Dominic said, the function you wrote doesn't work with any of those types. You can try passing spans created from your containers like this:
std::cout << getSize(std::span(arr));
std::cout << getSize(std::span(vec.begin(), vec.end()));
std::cout << getSize(std::span(il.begin(), il.end()));
std::cout << getSize(std::span(c_arr));
Or you can also template your function for c-style arrays and standard containers:
// For std containers
template<class T>
size_t getSize(T myContainer)
{
return std::span(myContainer.begin(), myContainer.end()).size();
}
// For c-style arrays
template<typename T, int n>
size_t getSize(T (&myArray)[n])
{
return std::span<T>(myArray).size();
}