Title
span's array constructor is too strict
Status
c++20
Section
[span.cons]
Submitter
Jean Guegant & Barry Revzin

Created on 2019-08-10.00:00:00 last changed 38 months ago

Messages

Date: 2020-02-14.09:37:04

Proposed resolution:

This wording is relative to N4849.

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

    template<class ElementType, size_t Extent = dynamic_extent>
    class span {
    public:
      […]
      // [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 [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.

Date: 2020-02-14.09:37:04

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

Date: 2020-02-13.00:00:00

[ 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 [span.overview], class template span synopsis, as indicated:

    template<class ElementType, size_t Extent = dynamic_extent>
    class span {
    public:
      […]
      // [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 [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.

Date: 2019-09-01.00:00:00

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

Date: 2019-08-17.19:20:46

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

  • A constructor template that takes any Container that is neither a raw array nor a std::array

  • A constructor template that takes an array<value_type, N>&

  • A constructor template that takes a const array<value_type, N>&

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 ([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 ([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 ([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 ([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.

History
Date User Action Args
2021-02-25 10:48:01adminsetstatus: wp -> c++20
2020-02-24 16:02:59adminsetstatus: immediate -> wp
2020-02-14 09:37:04adminsetmessages: + msg11107
2020-02-14 09:37:04adminsetstatus: new -> immediate
2020-02-13 18:29:40adminsetmessages: + msg11079
2019-09-02 12:43:36adminsetmessages: + msg10596
2019-08-17 19:20:46adminsetmessages: + msg10567
2019-08-10 00:00:00admincreate