3368. Exactly when does size return end - begin?

Section: 25.3.10 [range.prim.size] Status: Resolved Submitter: Casey Carter Opened: 2020-01-07 Last modified: 2021-02-25

Priority: 0

View all issues with Resolved status.

Discussion:

The specification of ranges::size in 25.3.10 [range.prim.size] suggests that bullet 1.3 ("Otherwise, make-unsigned-like(ranges::end(E) - ranges::begin(E)) ...") only applies when disable_sized_range<remove_cv_t<T>> is true. This is not the design intent, but the result of an erroneous attempt to factor out the common "disable_sized_range is false" requirement from the member and non-member size cases in bullets 1.2.1 and 1.2.2 that occurred between P0896R3 and P0896R4. The intended design has always been that a range with member or non-member size with the same syntax but different semantics may opt-out of being sized by specializing disable_sized_range. It has never been intended that arrays or ranges whose iterator and sentinel model sized_sentinel_for be able to opt out of being sized via disable_sized_range. disable_sized_sentinel_for can/must be used to opt out in the latter case so that library functions oblivious to the range type that operate on the iterator and sentinel of such a range will avoid subtraction.

[2020-01-25 Status set to Tentatively Ready after six positive votes on the reflector.]

[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]

[2020-11-18 This was actually resolved by P2091R0 in Prague. Status changed: WP → Resolved.]

Proposed resolution:

This wording is relative to N4842.

  1. Modify 25.3.10 [range.prim.size] as indicated:

    [Drafting note: There are drive-by changes here to (1) avoid introducing unused type placeholders, (2) avoid reusing "T" as both the type of the subexpression and the template parameter of the poison pill, and (3) fix the cross-reference for make-unsigned-like which is defined in [ranges.syn]/1, not in [range.subrange].]

    -1- The name size denotes a customization point object (16.3.3.3.5 [customization.point.object]). The expression ranges::size(E) for some subexpression E with type T is expression-equivalent to:

    1. (1.1) — decay-copy(extent_v<T>) if T is an array type (6.8.4 [basic.compound]).

    2. (1.2) — Otherwise, if disable_sized_range<remove_cv_t<T>> (25.4.3 [range.sized]) is false:

    3. (1.?2.1) — Otherwise, if disable_sized_range<remove_cv_t<T>> (25.4.3 [range.sized]) is false and decay-copy(E.size()) if it is a valid expression and its type I isof integer-like type (24.3.4.4 [iterator.concept.winc]), decay-copy(E.size()).

    4. (1.?2.2) — Otherwise, if disable_sized_range<remove_cv_t<T>> is false and decay-copy(size(E)) if it is a valid expression and its type I isof integer-like type with overload resolution performed in a context that includes the declaration:

      template<class T> void size(Tauto&&) = delete;
      
      and does not include a declaration of ranges::size, decay-copy(size(E)).

    5. (1.3) — Otherwise, make-unsigned-like(ranges::end(E) - ranges::begin(E)) (25.5.4 [range.subrange]25.2 [ranges.syn]) if it is a valid expression and the types I and S of ranges::begin(E) and ranges::end(E) (respectively) model both sized_sentinel_for<S, I> (24.3.4.8 [iterator.concept.sizedsentinel]) and forward_iterator<I>. However, E is evaluated only once.

    6. (1.4) — […]