无法将右值 std::array 转换为 std::span

Cannot convert rvalue std::array to std::span

考虑以下代码。

#include <array>
#include <span>

std::array<int, 2> Foo()
{
    return {1, 2};
}

int main()
{    
    std::array a = {3, 4};
    std::span s1 = a;
    
    // std::span s2 = Foo(); // Nope
    // std::span s3 = std::move(a); // Nope
    // std::span s4 = std::array<int, 2>{5, 6}; // Nope

    // MSVC 19.29: 'initializing': cannot convert from 'std::array<int,2>' to 'std::span<int,18446744073709551615>'
    // GCC 12.0.0: conversion from 'std::array<int, 2>' to non-scalar type 'std::span<int, 18446744073709551615>' requested
    // clang 13.0.0: actually compiles!
}

似乎 std::array 可以转换为一个 std::span,当它是一个仅在 clang 上的右值时。

我不确定这是否是问题的根源,但这是 MSVC 对 std::span.

std::array 相关构造函数的实现
    template <class _OtherTy, size_t _Size>
        requires (_Extent == dynamic_extent || _Extent == _Size)
              && is_convertible_v<_OtherTy (*)[], element_type (*)[]>
    constexpr span(array<_OtherTy, _Size>& _Arr) noexcept : _Mybase(_Arr.data(), _Size) {}

    template <class _OtherTy, size_t _Size>
        requires (_Extent == dynamic_extent || _Extent == _Size)
              && is_convertible_v<const _OtherTy (*)[], element_type (*)[]>
    constexpr span(const array<_OtherTy, _Size>& _Arr) noexcept : _Mybase(_Arr.data(), _Size) {}

乍一看我没有发现任何问题。右值应该绑定到 const 左值引用。

我的问题是:这段代码应该编译(这是前一个编译器的问题)还是不编译(这是后一个编译器的问题)?

我不知道 std::arraystd::span 的 r 值转换是否应该是一个编译错误,但这种转换很可能是一个错误,使用这样的跨度可能会导致 UB。

基本上,span 是查看器,而 array 是容器。 R 值被假定为临时值,因此,从临时容器初始化的 span 可能指向已销毁的数据片段,并且其用法未定义。

在某些情况下它是合法的操作 - 例如包装数组以进行调用。但总的来说,这是不安全和危险的。对于“array wrapping by span”,如果需要,你总是可以创建一个辅助函数。

长话短说;博士。这只是因为 libc++ 还没有实现 P1394.


问题是在 libstd++ and MSVC-STL 中,std::span 有以下 CTAD:

template<typename _Range>
    span(_Range &&)
      -> span<remove_reference_t<ranges::range_reference_t<_Range&>>>;

当我们调用std::span{std::array{0}}时,span的类型将被推导为span<int>,我们将调用span<int>::span(const array<int, 1>&),但是这个构造函数有following constraints:

Constraints: Let U be remove_­pointer_­t<decltype(data(arr))>.

  • extent == dynamic_­extent || N == extent is true, and
  • is_­convertible_­v<U(*)[], element_­type(*)[]> is true.

由于arr.data()的return类型是const int*element_typeint,所以is_convertible_v<const int(*)[], int(*)[]>的值为false,所以不满足约束条件。


此 CTAD 在 libc++ 中不可用,因此 std::span{std::array{0}} 将使用以下 CTAD:

template<class _Tp, size_t _Sz>
    span(const array<_Tp, _Sz>&) -> span<const _Tp, _Sz>;

然后调用 span<const int, 1>::span(array<const int, 1>&),这只是...工作。