3523. iota_view::sentinel is not always iota_view's sentinel

Section: 25.6.4.2 [range.iota.view] Status: C++23 Submitter: Tim Song Opened: 2021-02-17 Last modified: 2023-11-22

Priority: Not Prioritized

View all other issues in [range.iota.view].

View all issues with C++23 status.

Discussion:

P1739R4 added the following constructor to iota_view:

constexpr iota_view(iterator first, sentinel last) : iota_view(*first, last.bound_) {}

However, while iota_view's iterator type is always iota_view::iterator, its sentinel type is not always iota_view::sentinel. First, if Bound is unreachable_sentinel_t, then the sentinel type is unreachable_sentinel_t too - we don't add an unnecessary level of wrapping on top. Second, when W and Bound are the same type, iota_view models common_range, and the sentinel type is the same as the iterator type - that is, iterator, not sentinel.

Presumably the intent is to use the view's actual sentinel type, rather than always use the sentinel type.

[2021-03-12; Reflector poll]

Set status to Tentatively Ready after five votes in favour during reflector poll.

[2021-06-07 Approved at June 2021 virtual plenary. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4878.

  1. Edit 25.6.4.2 [range.iota.view], as indicated:

    namespace std::ranges {
      // [...]
    
      template<weakly_­incrementable W, semiregular Bound = unreachable_sentinel_t>
        requires weakly-equality-comparable-with<W, Bound> && semiregular<W>
      class iota_view : public view_interface<iota_view<W, Bound>> {
      private:
        // [range.iota.iterator], class iota_­view​::​iterator
        struct iterator;            // exposition only
        // [range.iota.sentinel], class iota_­view​::​sentinel
        struct sentinel;            // exposition only
        W value_ = W();             // exposition only
        Bound bound_ = Bound();     // exposition only
      public:
        iota_view() = default;
        constexpr explicit iota_view(W value);
        constexpr iota_view(type_identity_t<W> value,
                            type_identity_t<Bound> bound);
        constexpr iota_view(iterator first, sentinelsee below last);: iota_view(*first, last.bound_) {}
    
        constexpr iterator begin() const;
        constexpr auto end() const;
        constexpr iterator end() const requires same_­as<W, Bound>;
    
        constexpr auto size() const requires see below;
      };
    
      template<class W, class Bound>
          requires (!is-integer-like<W> || !is-integer-like<Bound> ||
                  (is-signed-integer-like<W> == is-signed-integer-like<Bound>))
          iota_view(W, Bound) -> iota_view<W, Bound>;
    }
    

    [...]

    constexpr iota_view(type_identity_t<W> value, type_identity_t<Bound> bound);
    

    -8- Preconditions: Bound denotes unreachable_­sentinel_­t or bound is reachable from value. When W and Bound model totally_­ordered_­with, then bool(value <= bound) is true.

    -9- Effects: Initializes value_­ with value and bound_ with bound.

    constexpr iota_view(iterator first, see below last);
    

    -?- Effects: Equivalent to:

    1. (?.1) — If same_as<W, Bound> is true, iota_view(first.value_, last.value_).

    2. (?.2) — Otherwise, if Bound denotes unreachable_sentinel_t, iota_view(first.value_, last).

    3. (?.3) — Otherwise, iota_view(first.value_, last.bound_).

    -?- Remarks: The type of last is:

    1. (?.1) — If same_as<W, Bound> is true, iterator.

    2. (?.2) — Otherwise, if Bound denotes unreachable_sentinel_t, Bound.

    3. (?.3) — Otherwise, sentinel.