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

5

请看下面的代码。

#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!
}

似乎在clang中,只有当std::array是右值时才能将其转换为std::span
我不确定这是否是问题的根源,但以下是std::span的构造函数与std::array相关的MSVC实现。
    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) {}

乍一看,我没有发现任何问题。rvalue 应该与 const lvalue 引用绑定。

我的问题是:这段代码是否应该编译(如果是旧编译器的问题)或者不编译(如果是新编译器的问题)?


1
它无法编译是一件好事。若允许这种情况,std::span 将指向已被清理的内存。你认为以下代码是有效的吗:std::string_view sv{ std::string{ "Hello there" } }?像 std::string_view 一样,std::span 不会拥有该对象。 - WBuck
@康桓瑋,啊,好的。clang 12也不接受它。https://godbolt.org/z/r3sz6nzE1。不过,clang 12接受span<int const, 2> s2 = Foo();(这会让你陷入更多麻烦)。无论如何,我必须说:“span”是多么可怕、语义模糊的名称和设计! - alfC
@alfC。您需要打开-stdlib=libc++标志。https://godbolt.org/z/b3YnEvx3j - 康桓瑋
以上代码是否编译通过? - Galik
1
有效(我指的是没有生命周期问题)的示例用法将是 void print(std::span<const int>) - Jarod42
显示剩余3条评论
2个回答

5

简而言之,这是因为libc++尚未实现P1394


问题在于在libstd++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>&),但是此构造函数有以下约束条件:让Uremove_­pointer_­t<decltype(data(arr))>
  • extent == dynamic_­extent || N == extenttrue,且
  • is_­convertible_­v<U(*)[], element_­type(*)[]>true
由于arr.data()的返回类型为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>&),这只是一项...工作。


1
我不知道将std::array的r-value转换为std::span是否应该是编译错误,但这样的转换很可能是一个错误,并且使用这样的将很可能导致UB。

基本上,span是一个查看器,而array是一个容器。R-values被认为是临时的,因此,从临时容器初始化的可能会指向已销毁的数据片段,其使用是未定义的。

有些情况下这是合法的操作,比如在调用时包装一个数组。但一般来说,这是不安全和危险的。对于“通过包装数组”,如果需要,您可以随时创建一个帮助函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接