3103. Errors in taking subview of span should be ill-formed where possible

Section: 23.7.2.2.4 [span.sub] Status: C++20 Submitter: Tomasz Kamiński Opened: 2018-04-13 Last modified: 2021-02-25

Priority: 3

View all issues with C++20 status.

Discussion:

Currently all out-of-bound/inputs errors in the functions taking an subview of span lead to undefined behavior, even in the situation when they could be detected at compile time. This is inconsistent with the behavior of the span constructors, which make similar constructs ill-formed.

Furthermore, with the current specification of the subspan function, the following invocation:

span<T, N> s;   // N > 0
s.subspan<O>(); // with O > 0

is ill-formed when O > N + 1, as the return of the function is span<T, K> with K < -1. However in case when O == N + 1, runtime sized span is returned (span<T, -1>) instead and the behavior of the function is undefined.

Firstly, for either run time sized (N == dynamic_extent) and fixed sized (N > 0) object s of type span<T, N>, the following constructs should be ill-formed, instead of having undefined behavior:

  1. s.first<C>() with C < 0

  2. s.last<C>() with C < 0

  3. s.subspan<O, E> with O < 0 or E < 0 and E != dynamic_extent.

This would follow span specification, that make instantiation of span<T, N> ill-formed for N < 0 and N != dynamic_extent.

In addition the following constructs should be made ill-formed for fixed size span s of type span<T, N> (with N > 0):

  1. s.first<C>() with C > N

  2. s.last<C>() with C > N

  3. s.subspan<O, dynamic_extent>() with O > N

  4. s.subspan<O, C>() with O + C > N

This will match the span constructor that made construction of fixed size span<T, N> from fixed size span of different size ill-formed.

[2018-04-24 Priority set to 3 after discussion on the reflector.]

[2018-11 San Diego Thursday night issue processing]

Tomasz to provide updated wording.

Previous resolution: [SUPERSEDED]

This wording is relative to N4741.

  1. Edit 23.7.2.2.4 [span.sub] as indicated:

    template<ptrdiff_t Count> constexpr span<element_type, Count> first() const;
    

    -?- Remarks: If Count < 0 || (Extent != dynamic_extent && Count > Extent), the program is ill-formed.

    -1- Requires: 0 <= Count && Count <= size().

    -2- Effects: Equivalent to: return {data(), Count};

    template<ptrdiff_t Count> constexpr span<element_type, Count> last() const;
    

    -?- Remarks: If Count < 0 || (Extent != dynamic_extent && Count > Extent), the program is ill-formed.

    -3- Requires: 0 <= Count && Count <= size().

    -4- Effects: Equivalent to: return {data() + (size() - Count), Count};

    template<ptrdiff_t Offset, ptrdiff_t Count = dynamic_extent>
      constexpr span<element_type, see below> subspan() const;
    

    -?- Remarks: The program is ill-formed if:

    • Offset < 0 || (Count < 0 && Count != dynamic_extent), or

    • Extend != dynamic_extent && (Offset > Extent || (Count != dynamic_extent && Offset + Count > Extent)).

    -5- Requires: (0 <= Offset && Offset <= size()) && (Count == dynamic_extent || Count >= 0 && Offset + Count <= size()).

    -6- Effects: Equivalent to: return span<ElementType, see below>( data() + Offset, Count != dynamic_extent ? Count : size() - Offset);

    -7- Remarks: The second template argument of the returned span type is:

    Count != dynamic_extent ? Count
                            : (Extent != dynamic_extent ? Extent - Offset
                                                        : dynamic_extent)
    

[2018-11-09; Tomasz provides updated wording]

I have decided to replace all Requires: elements in the section 23.7.2.2.4 [span.sub] to preserve consistency.

Previous resolution: [SUPERSEDED]

This wording is relative to N4778.

  1. Edit 23.7.2.2.4 [span.sub] as indicated:

    template<ptrdiff_t Count> constexpr span<element_type, Count> first() const;
    

    -?- Mandates: Count >= 0 && (Extent == dynamic_extent || Count <= Extent).

    -1- RequiresExpects: 0 <= Count && Count <= size().

    -2- Effects: Equivalent to: return {data(), Count};

    template<ptrdiff_t Count> constexpr span<element_type, Count> last() const;
    

    -?- Mandates: Count >= 0 && (Extent == dynamic_extent || Count <= Extent).

    -3- RequiresExpects: 0 <= Count && Count <= size().

    -4- Effects: Equivalent to: return {data() + (size() - Count), Count};

    template<ptrdiff_t Offset, ptrdiff_t Count = dynamic_extent>
      constexpr span<element_type, see below> subspan() const;
    

    -?- Mandates: Offset >= 0 && (Count >= 0 || Count == dynamic_extent) && (Extent == dynamic_extent || (Offset <= Extent && (Count == dynamic_extent || Offset + Count <= Extent))).

    -5- RequiresExpects: (0 <= Offset && Offset <= size()) && (Count == dynamic_extent || Count >= 0 && Offset + Count <= size()).

    -6- Effects: Equivalent to: return span<ElementType, see below>( data() + Offset, Count != dynamic_extent ? Count : size() - Offset);

    -7- Remarks: The second template argument of the returned span type is:

    Count != dynamic_extent ? Count
                            : (Extent != dynamic_extent ? Extent - Offset
                                                        : dynamic_extent)
    
    constexpr span<element_type, dynamic_extent> first(index_type count) const;
    

    -8- RequiresExpects: 0 <= count && count <= size().

    -9- Effects: Equivalent to: return {data(), count};

    constexpr span<element_type, dynamic_extent> last(index_type count) const;
    

    -10- RequiresExpects: 0 <= count && count <= size().

    -11- Effects: Equivalent to: return {data() + (size() - count), count};

    constexpr span<element_type, dynamic_extent> subspan(
      index_type offset, index_type count = dynamic_extent) const;
    

    -12- RequiresExpects: (0 <= offset && offset <= size()) && (count == dynamic_extent || count >= 0 && offset + count <= size())

    -13- Effects: Equivalent to: return {data() + offset, count == dynamic_extent ? size() - offset : count};

[2019-06-23; Tomasz comments and provides updated wording]

The current proposed resolution no longer applies to the newest revision of the standard (N4820), due changes introduced in P1227 (making size() and template parameters of span unsigned).

[2019 Cologne Wednesday night]

Status to Ready

Proposed resolution:

This wording is relative to N4820.

[Drafting note: This wording relies on observation, that the condition in form Extent == dynamic_extent || Count <= Extent, can be simplified into Count <= Extent, because dynamic_extent is equal to numeric_limits<size_t>::max(), thus size() <= Extent is always true, and Extent == dynamic_extent implies that Count <= Extent.

Furthermore we check that Count != dynamic_extent || Count <= Extent - Offset, as the Offset + Count <= Extent may overflow (defined for unsigned integers) and produce false positive result. This change is also applied to Expects clause. ]

  1. Edit 23.7.2.2.4 [span.sub] as indicated:

    template<size_t Count> constexpr span<element_type, Count> first() const;
    

    -?- Mandates: Count <= Extent is true.

    -1- Expects: Count <= size() is true.

    -2- Effects: Equivalent to: return {data(), Count};

    template<size_t Count> constexpr span<element_type, Count> last() const;
    

    -?- Mandates: Count <= Extent is true.

    -3- Expects: Count <= size() is true.

    -4- Effects: Equivalent to: return {data() + (size() - Count), Count};

    template<size_t Offset, size_t Count = dynamic_extent>
      constexpr span<element_type, see below> subspan() const;
    

    -?- Mandates: Offset <= Extent && (Count == dynamic_extent || Count <= Extent - Offset) is true.

    -5- Expects: Offset <= size() && (Count == dynamic_extent || Offset + Count <= size()Count <= size() - Offset) is true.

    -6- Effects: Equivalent to: return span<ElementType, see below>(data() + Offset, Count != dynamic_extent ? Count : size() - Offset);

    -7- Remarks: The second template argument of the returned span type is:

    Count != dynamic_extent ? Count
                            : (Extent != dynamic_extent ? Extent - Offset
                                                        : dynamic_extent)
    
    […]
    constexpr span<element_type, dynamic_extent> subspan(
      index_type offset, index_type count = dynamic_extent) const;
    

    -12- Expects: offset <= size() && (count == dynamic_extent || offset + count <= size()count <= size() - offset) is true.

    -13- Effects: Equivalent to: return {data() + offset, count == dynamic_extent ? size() - offset : count};