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)?
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
Uberemove_pointer_t<decltype(data(arr))>.
extent == dynamic_extent || N == extentistrue, andis_convertible_v<U(*)[], element_type(*)[]>istrue.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With