Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a C-style array to `span<T>`

Tags:

c++

c++20

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>?

like image 670
Ranoiaetep Avatar asked Nov 25 '21 22:11

Ranoiaetep


People also ask

What is the difference between array and span in C++?

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.

How do I create a span from an array?

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:

Why use span<t> instead of an 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.

What happens when you pass an array to a function?

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.


2 Answers

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.

like image 135
Dominic Price Avatar answered Oct 21 '22 05:10

Dominic Price


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();
}
like image 16
JorgeLDB Avatar answered Oct 21 '22 04:10

JorgeLDB