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

Viewed 831

Consider the following code.

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

It seems like std::array can be converted to an std::span when it's an rvalue only on clang.

I'm not sure if this is the root of the issue but here's MSVC's implementation of std::array-related constructors of 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) {}

At first glance I don't see anything wrong with it. An rvalue should bind to a const lvalue reference.

My question is: should this code compile (and it's an issue with the former compilers) or not (and it's an issue with the latter compiler)?

2 Answers

TL;DR. This is just because libc++ has not yet implemented P1394.


The problem is that in libstd++ and MSVC-STL, std::span has the following CTAD:

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

When we call std::span{std::array{0}}, the type of span will be deduced to span<int>, and we will call span<int>::span(const array<int, 1>&), but this constructor has the 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.

Since the return type of arr.data() is const int* and element_type is int, the value of is_convertible_v<const int(*)[], int(*)[]> is false, so the constraint is not satisfied.


This CTAD is not available in libc++, so std::span{std::array{0}} will use the following CTAD:

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

And call span<const int, 1>::span(array<const int, 1>&), which is just... work.

I don't know whether r-value conversion of std::array to std::span should be a compilation error or not but such conversion is most likely a bug and usage of such a span will probably cause UB.

Basically, span is a viewer while array is a container. R-values are presumed to be temporaries, therefore, span initialized from a temporary container is likely to point towards destroyed piece of data and it's usage is undefined.

There are cases where it is a legitimate operation - like wrapping an array for a call. But generally, it is unsafe and dangerous. For the "array wrapping by span" you can always create a helper function if needed.

Related