Home > Enterprise >  Passing c-style array to `span<T>`
Passing c-style array to `span<T>`

Time:11-26

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*'
}

Godbolt

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();
}
  • Related