Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

std

stl

c++20

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

like image 384
Vennor Avatar asked Apr 27 '26 02:04

Vennor


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.

like image 148
康桓瑋 Avatar answered Apr 29 '26 17:04

康桓瑋


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.

like image 34
ALX23z Avatar answered Apr 29 '26 16:04

ALX23z