C++20 introduced std::span
, which is a view-like object that can take in a continuous sequence, such as a C-style array, std::array
, and std::vector
. A common problem with a C-style array is it will decay to a pointer when passing to a function. Such a 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 type. 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*'
}
Godbolt
Is this the correct behavior? If so, is there a way to accept a C-style array with span<T>
?
A std::array is fixed in size, and a span doesn't even manage the memory of the block it points to, it just points to the block of memory, knows how long the block of memory is, knows what data type is in a C-array in the memory, and provides convenience accessor functions to work with the elements in that contiguous memory.
For example, you can create a Span<T> from an array: From there, you can easily and efficiently create a span to represent/point to just a subset of this array, utilizing an overload of the span’s Slice method. From there you can index into the resulting span to write and read data in the relevant portion of the original array:
From this brief description, two things should be clear: Span<T> is defined in such a way that operations can be as efficient as on arrays: indexing into a span doesn’t require computation to determine the beginning from a pointer and its starting offset, as the ref field itself already encapsulates both.
When you pass a C-style array to a function it will decay to a pointer to the first element of the array, basically losing the size information. As a side note, in a pure C++ code, one will prefer to use std::vector or std::array instead of C-style arrays.
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 an 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.
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();
}
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