1

I have the following code, which I expected to work, but it doesn't:

#include <array>
#include <span>

auto f = std::span<int>(std::array<int, 3>{});

clang fails to compile this:

error: no matching conversion for functional-style cast from 'std::array<int, 3>' to 'std::span<int>'
auto f = std::span<int>(std::array<int, 3>{});
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

note: candidate template ignored: constraints not satisfied [with _Tp = int, _ArrayExtent = 3]
        span(const array<_Tp, _ArrayExtent>& __arr) noexcept
        ^

I expected this to call the constructor:

template< class U, std::size_t N >
constexpr span( const std::array<U, N>& arr ) noexcept; // (6)

4-6) Constructs a span that is a view over the array arr; the resulting span has size() == N and data() == std::data(arr).

These overloads participate in overload resolution only if extent == std::dynamic_extent || N == extent is true and the conversion from std::remove_pointer_t<decltype(data(arr))> to element_type is at most a qualification conversion.

See std::span::span on cppreference

Why are the constraints of this constructor not satisfied?

  • extent == std::dynamic_extent for a std::span<int>, so the first requirement is obviously met
  • std::remove_pointer_t<decltype(data(arr))> is int, which is equal to std::span<int>::element_type = int, so the second requirement is also met

I don't see any reason why I wouldn't be able to call this constructor.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • Even if this compiled, it would immediately dangle... – Barry Jun 23 '23 at 14:23
  • @Barry well, the original example involved passing to a function that accepts `std::span` as a parameter and concatenates the contents with `std::move` and `operator+`. Perhaps I've made it a little bit too minimal. – Jan Schultke Jun 23 '23 at 14:49

1 Answers1

2

Qualification conversions can only make something more const qualified.

decltype(data(arr)) is const int* for const std::array<int, 3>&. const int can not be converted to int via a qualification conversion.

std::span<const int>(std::array<int, 3>{}) does work however (const int -> const int).

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • What is the most brief way of constructing a `std::span` from a temporary array then, or is there no one-liner? – Jan Schultke Jun 23 '23 at 12:10
  • @JanSchultke `auto& unmove(auto&& temporary) { return temporary; }` is a good way to get a non-const lvalue for a temporary. So `std::span(unmove(std::array{}))`, or `std::span([](auto&& arr)->auto&{return arr;}(std::array{}))` if you want that one liner. But this isn't useful outside of a very specific set of circumstances since the array immediately dies. Why not just use `auto f = std::array{}` (which is implicitly convertible to a span)? – Artyer Jun 23 '23 at 12:18
  • It's supposed to be a demonstration of how you can avoid template parameter packs in favor of `std::span`, `std::initializer_list`, `std::array`, etc. – Jan Schultke Jun 23 '23 at 12:36
  • `std::initializer_list` is also const, so you are probably looking for a const span. And maybe two overloads: `void f(std::span); inline void f(std::initializer_list ilist) { f(std::span{ilist}); }` – Artyer Jun 23 '23 at 12:48
  • I was looking for the opposite actually, because I want to move from the span, and a `std::initializer_list` or `std::span` don't really let you do that – Jan Schultke Jun 23 '23 at 12:52