This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++20 status.

3255. span's array constructor is too strict

Section: 24.7.2.2.2 [span.cons] Status: C++20 Submitter: Jean Guegant & Barry Revzin Opened: 2019-08-10 Last modified: 2021-02-25

Priority: 2

View all other issues in [span.cons].

View all issues with C++20 status.

Discussion:

Barry Revzin:

From StackOverflow:

This compiles:

std::vector<int*> v = {nullptr, nullptr};
std::span<const int* const> s{v};

This does not:

std::array<int*, 2> a = {nullptr, nullptr};
std::span<const int* const> s{a};

The problem is that span's constructors include

So the first is excluded, and the other two don't match. We can change the array constructor templates to take an array<T, N> with the requirement that T(*)[] is convertible to ElementType(*)[]?

Jean Guegant:

It is impossible to create a std::span from a std::array<const T, X> given the current set of constructors of std::span (24.7.2.2.2 [span.cons]):

std::array<const int, 4> a = {1, 2, 3, 4};
std::span<const int> s{a}; // No overload can be found.
std::span s{a}; // CTAD doesn't help either.

Both constructors accepting a std::array (24.7.2.2.2 [span.cons] p11) require the first template parameter of the std::array parameter to be value_type:

template<size_t N> constexpr span(array<value_type, N>& arr) noexcept;
template<size_t N> constexpr span(const array<value_type, N>& arr) noexcept;

value_type being defined as remove_cv_t<ElementType> — this constrains the first template parameter not to be const.

Both constructors accepting a generic Container (24.7.2.2.2 [span.cons] p14) have a constraint — (p14.3) Container is not a specialization of array — rejecting std::array.

While you can call std::array<const T, X>::data and std::array<const T, X>::size to manually create a std::span, we should, in my opinion, offer a proper overload for this scenario. Two reasons came to my mind:

  1. std::span handles C-arrays and std::arrays in an asymmetric way. The constructor taking a C-array (24.7.2.2.2 [span.cons] p11) is using element_type and as such can work with const T:

    const int a[] = {1, 2, 3, 4};
    std::span<const int> s{a}; // It works
    

    If a user upgrades her/his code from C-arrays to a std::arrays and literally take the type const T and use it as the first parameter of std::array, he/she will face an error.

  2. Even if the user is aware that const std::array<T, X> is more idiomatic than std::array<const T, X>, the second form may appear in the context of template instantiation.

At the time this issue is written gls::span, from which std::span is partly based on, does not suffer from the same issue: Its constructor taking a generic const Container& does not constraint the Container not to be a std::array (although its constructor taking a generic Container& does). For the users willing to upgrade from gsl::span to std::span, this could be a breaking change.

[2019-09-01 Priority set to 2 based on reflector discussion]

[2020-02-13 Tim updates PR]

The previous PR's change to the raw array constructor is both 1) unnecessary and 2) incorrect; it prevents span<const int> from being initialized with an int[42] xvalue.

Previous resolution: [SUPERSEDED]

This wording is relative to N4830.

The only change is to make the constructors templated on the element type of the array as well. We already have the right constraints in place. It's just that the 2nd constraint is trivially satisfied today by the raw array constructor and either always or never satisfied by the std::array one.

  1. Modify 24.7.2.2.1 [span.overview], class template span synopsis, as indicated:

    template<class ElementType, size_t Extent = dynamic_extent>
    class span {
    public:
      […]
      // 24.7.2.2.2 [span.cons], constructors, copy, and assignment
      constexpr span() noexcept;
      constexpr span(pointer ptr, index_type count);
      constexpr span(pointer first, pointer last);
      template<class T, size_t N>
        constexpr span(Telement_type (&arr)[N]) noexcept;
      template<class T, size_t N>
        constexpr span(array<Tvalue_type, N>& arr) noexcept;
      template<class T, size_t N>
        constexpr span(const array<Tvalue_type, N>& arr) noexcept;
      […]
    };
    
  2. Modify 24.7.2.2.2 [span.cons] as indicated:

    template<class T, size_t N>
      constexpr span(Telement_type (&arr)[N]) noexcept;
    template<class T, size_t N>
      constexpr span(array<Tvalue_type, N>& arr) noexcept;
    template<class T, size_t N>
      constexpr span(const array<Tvalue_type, N>& arr) noexcept;
    

    -11- Constraints:

    1. (11.1) — extent == dynamic_extent || N == extent is true, and

    2. (11.2) — remove_pointer_t<decltype(data(arr))>(*)[] is convertible to ElementType(*)[].

    -12- Effects: Constructs a span that is a view over the supplied array.

    -13- Ensures: size() == N && data() == data(arr) is true.

[2020-02 Status to Immediate on Thursday night in Prague.]

Proposed resolution:

This wording is relative to N4849.

  1. Modify 24.7.2.2.1 [span.overview], class template span synopsis, as indicated:

    template<class ElementType, size_t Extent = dynamic_extent>
    class span {
    public:
      […]
      // 24.7.2.2.2 [span.cons], constructors, copy, and assignment
      constexpr span() noexcept;
      […]
      template<size_t N>
        constexpr span(element_type (&arr)[N]) noexcept;
      template<class T, size_t N>
        constexpr span(array<Tvalue_type, N>& arr) noexcept;
      template<class T, size_t N>
        constexpr span(const array<Tvalue_type, N>& arr) noexcept;
      […]
    };
    
  2. Modify 24.7.2.2.2 [span.cons] as indicated:

    template<size_t N>
      constexpr span(element_type (&arr)[N]) noexcept;
    template<class T, size_t N>
      constexpr span(array<Tvalue_type, N>& arr) noexcept;
    template<class T, size_t N>
      constexpr span(const array<Tvalue_type, N>& arr) noexcept;
    

    -11- Constraints:

    1. (11.1) — extent == dynamic_extent || N == extent is true, and

    2. (11.2) — remove_pointer_t<decltype(data(arr))>(*)[] is convertible to ElementType(*)[].

    -12- Effects: Constructs a span that is a view over the supplied array.

    -13- Postconditions: size() == N && data() == data(arr) is true.