将 C 风格的数组传递给 `span<T>`

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

C++20引入了std::span,它是一个类视图对象,可以接受连续的序列,比如C风格的数组,std::arraystd::vector. C 风格数组的一个常见问题是它在传递给函数时会衰减为指针。这样的问题可以用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);
}

这将按预期打印 5555。但是,size 可能不应该只接收 int 的容器。相反,它应该接受任何类型的容器。但是,将 size 更改为接受 std::span<T> 的模板函数后,它无法再成功替换 C 样式数组,而其他可以:

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

这是正确的行为吗?如果是这样,有没有办法接受带有 span<T> 的 C 样式数组?

问题不在于为什么 int[] 会失败,而在于为什么它适用于所有其他类型!不幸的是,您已成为 ADL 的牺牲品,它实际上调用的是 std::size 而不是您编写的 size 函数。这是因为您的函数的所有重载都失败了,因此它会在第一个参数的命名空间中查找匹配函数,并在其中找到 std::size。重新运行您的程序,将函数重命名为其他名称:

template<typename T>
size_t my_size(std::span<T> s)
{
    return s.size();
}

在 GCC 12 上我得到

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);
      |                  ~~~~~~~^~~~~

加上所有其他类型的类似错误。如果您的问题是为什么会失败;那么简短的回答是模板类型推导比常规类型推导严格得多,所以除非你给它一个完全匹配(或多或少),否则它会失败。有关更详细的解释,请阅读类似的问题,例如 处理类似的问题。

好吧,正如 ,您编写的函数不适用于任何这些类型。您可以尝试传递从您的容器创建的跨度,如下所示:

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));

或者您也可以为 C 风格数组和标准容器模板化您的函数:

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