3749. common_iterator should handle integer-class difference types

Section: 24.5.5 [iterators.common] Status: WP Submitter: Hewill Kang Opened: 2022-08-01 Last modified: 2023-11-22

Priority: 2

View all issues with WP status.

Discussion:

The partial specialization of iterator_traits for common_iterator is defined in 24.5.5.1 [common.iterator] as

template<input_iterator I, class S>
struct iterator_traits<common_iterator<I, S>> {
  using iterator_concept = see below;
  using iterator_category = see below;
  using value_type = iter_value_t<I>;
  using difference_type = iter_difference_t<I>;
  using pointer = see below;
  using reference = iter_reference_t<I>;
};

where difference_type is defined as iter_difference_t<I> and iterator_category is defined as at least input_iterator_tag. However, when difference_type is an integer-class type, common_iterator does not satisfy Cpp17InputIterator, which makes iterator_category incorrectly defined as input_iterator_tag.

Since the main purpose of common_iterator is to be compatible with the legacy iterator system, which is reflected in its efforts to try to provide the operations required by C++17 iterators even if the underlying iterator does not support it. We should handle this case of difference type incompatibility as well.

The proposed solution is to provide a C++17 conforming difference type by clamping the integer-class type to ptrdiff_t.

Daniel:

The second part of this issue provides an alternative resolution for the first part of LWG 3748 and solves the casting problem mentioned in LWG 3748 as well.

[2022-08-23; Reflector poll]

Set priority to 2 after reflector poll.

"I think common_iterator should reject iterators with integer-class difference types since it can't possibly achieve the design intent of adapting them to Cpp17Iterators."

"I'm not yet convinced that we need to outright reject such uses, but I'm pretty sure that we shouldn't mess with the difference type and that the PR is in the wrong direction."

Previous resolution [SUPERSEDED]:

This wording is relative to N4910.

  1. Modify 24.5.5.1 [common.iterator] as indicated:

    [Drafting note: common_iterator requires iterator type I must model input_or_output_iterator which ensures that iter_difference_t<I> is a signed-integer-like type. The modification of common_iterator::operator- is to ensure that the pair of common_iterator<I, S> models sized_sentinel_for when sized_sentinel_for<I, S> is modeled for iterator type I with an integer-class difference type and its sentinel type S.]

    namespace std {
      template<class D>
        requires is-signed-integer-like<D>
      using make-cpp17-diff-t = conditional_t<signed_integral<D>, D, ptrdiff_t>;  // exposition only
    
      template<input_or_output_iterator I, sentinel_for<I> S>
        requires (!same_as<I, S> && copyable<I>)
      class common_iterator {
      public:
        […]
        template<sized_sentinel_for<I> I2, sized_sentinel_for<I> S2>
          requires sized_sentinel_for<S, I2>
        friend constexpr make-cpp17-diff-t<iter_difference_t<I2>> operator-(
          const common_iterator& x, const common_iterator<I2, S2>& y);
        […]
      };
      
      template<class I, class S>
      struct incrementable_traits<common_iterator<I, S>> {
        using difference_type = make-cpp17-diff-t<iter_difference_t<I>>;
      };
    
      template<input_iterator I, class S>
      struct iterator_traits<common_iterator<I, S>> {
        using iterator_concept = see below;
        using iterator_category = see below;
        using value_type = iter_value_t<I>;
        using difference_type = make-cpp17-diff-t<iter_difference_t<I>>;
        using pointer = see below;
        using reference = iter_reference_t<I>;
      };
    }
    
  2. Modify 24.5.5.6 [common.iter.cmp] as indicated:

    [Drafting note: If this issue is voted in at the same time as LWG 3748, the editor is kindly informed that the changes indicated below supersede those of the before mentioned issues first part.]

    template<sized_sentinel_for<I> I2, sized_sentinel_for<I> S2>
      requires sized_sentinel_for<S, I2>
    friend constexpr make-cpp17-diff-t<iter_difference_t<I2>> operator-(
      const common_iterator& x, const common_iterator<I2, S2>& y);
    

    -5- Preconditions: x.v_.valueless_by_exception() and y.v_.valueless_by_exception() are each false.

    -6- Returns: 0 if i and j are each 1, and otherwise static_cast<make-cpp17-diff-t<iter_difference_t<I2>>>(get<i>(x.v_) - get<j>(y.v_)), where i is x.v_.index() and j is y.v_.index().

[2023-06-13; Varna; Tomasz provides wording]

[2023-06-14 Varna; Move to Ready]

[2023-11-11 Approved at November 2023 meeting in Kona. Status changed: Voting → WP.]

Proposed resolution:

This wording is relative to N4950.

  1. Modify 24.5.5.1 [common.iterator] as indicated:

    namespace std {
      […]
    
      template<input_iterator I, class S>
      struct iterator_traits<common_iterator<I, S>> {
        using iterator_concept = see below;
        using iterator_category = see below; // not always present
        using value_type = iter_value_t<I>;
        using difference_type = iter_difference_t<I>;
        using pointer = see below;
        using reference = iter_reference_t<I>;
      };
    }
    
  2. Modify 24.5.5.2 [common.iter.types] as indicated:

    -?- The nested typedef-name iterator_category of the specialization of iterator_traits for common_iterator<I, S> is defined if and only if iter_difference_t<I> is an integral type. In that case, iterator_category denotes forward_iterator_tag if the qualified-id iterator_traits<I>::iterator_category is valid and denotes a type that models derived_from<forward_iterator_tag>; otherwise it denotes input_iterator_tag.

    -1- The remaining nested typedef-names of the specialization of iterator_traits for common_iterator<I, S> are defined as follows.:

    1. (1.1) — iterator_concept denotes forward_iterator_tag if I models forward_iterator; otherwise it denotes input_iterator_tag.

    2. (1.2) — iterator_category denotes forward_iterator_tag if the qualified-id iterator_traits<I>::iterator_category is valid and denotes a type that models derived_from<forward_iterator_tag>; otherwise it denotes input_iterator_tag.

    3. (1.3) — Let a denote an lvalue of type const common_iterator<I, S>. If the expression a.operator->() is well-formed, then pointer denotes decltype(a.operator->()). Otherwise, pointer denotes void.