【问题标题】:Cannot convert rvalue std::array to std::span无法将右值 std::array 转换为 std::span
【发布时间】:2021-10-10 00:38:33
【问题描述】:

考虑下面的代码。

#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::array 相关构造函数的 std::span 的实现。

    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 左值引用。

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

【问题讨论】:

  • 这是一个更大问题的简化案例,为了清楚起见,我不在这里展示,因此它可能看起来像一个糟糕的用例,我明白这一点。
  • 有时简化版会过多地改变问题的性质,您可能会得到无法使用的答案。
  • @user4581301 谢谢,但在这种情况下它不会因为我只对代码是否应该编译感兴趣。
  • 不编译是件好事。如果允许,std::span 将指向已清理的内存。您认为此代码有效吗:std::string_view sv{ std::string{ "Hello there" } }?。像std::string_view 这样的std::span 不获取对象的所有权。
  • 有效(我的意思是没有生命周期问题)示例用法为void print(std::span&lt;const int&gt;)

标签: c++ stl std c++20


【解决方案1】:

TL;博士。这只是因为libc++还没有实现 p1394r4.


问题是在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&lt;int&gt;,我们会调用span&lt;int&gt;::span(const array&lt;int, 1&gt;&amp;),但是这个构造函数有following constraints

约束:让U 成为remove_­pointer_­t&lt;decltype(data(arr))&gt;

  • extent == dynamic_­extent || N == extenttrue,并且
  • is_­convertible_­v&lt;U(*)[], element_­type(*)[]&gt;true

由于arr.data()的返回类型为const int*element_typeint,所以is_convertible_v&lt;const int(*)[], int(*)[]&gt;的值为false,因此不满足约束。


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

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

然后打电话给span&lt;const int, 1&gt;::span(array&lt;const int, 1&gt;&amp;),这只是……工作。

【讨论】:

    【解决方案2】:

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

    基本上,span 是查看器,而 array 是容器。 R 值被假定为临时值,因此,从临时容器初始化的 span 很可能指向已损坏的数据,其用途未定义。

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

    【讨论】:

      猜你喜欢
      • 2016-08-14
      • 2021-03-02
      • 2021-01-04
      • 2012-05-23
      • 2018-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多