3289. Cannot opt out of C++17 iterator-ness without also opting out of C++20 iterator-ness

Section: 24.3.2.3 [iterator.traits], 24.5.5.2 [common.iter.types] Status: Resolved Submitter: Eric Niebler Opened: 2019-09-11 Last modified: 2021-05-18

Priority: 2

View all other issues in [iterator.traits].

View all issues with Resolved status.

Discussion:

The way to non-intrusively say that a type doesn't satisfy the C++17 iterator requirements is to specialize std::iterator_traits and not provide the nested typedefs. However, if a user were to do that, she would also be saying that the type is not a C++20 iterator. That is because readable and weakly_incrementable are specified in terms of iter_value_t<I> and iter_difference_t<I>. Those aliases check to see if std::iterator_traits<I> has been specialized (answer: yes), and if so resolve to std::iterator_traits<I>::value_type and std::iterator_traits<I>::difference_type respectively.

The proper way to opt out of C++17 iterator-ness while opting in to C++20 iterator-ness would be to specialize std::iterator_traits and specify all the nested typedefs except ::iterator_category. That's a bit weird and may throw off code that is expecting all the typedefs to be there, or none of them, so instead we can suggest users to set the iterator_category typedef to denote output_iterator_tag, which is a harmless lie; generic C++17 code will get the message: this iterator is not a c++17 input iterator, which is the salient bit.

We then must fix up all the places in the Ranges clause that make faulty assumptions about an iterator's iterator_category typedef (as distinct from the iterator concept that it models).

[2019-10-19 Issue Prioritization]

Priority to 1 after reflector discussion.

[2019-11 Wednesday night Issue processing in Belfast]

Much discussion; no consensus that this is a good approach. Need to coordinate between this and 3283

Previous resolution [SUPERSEDED]:

This wording is relative to N4830.

  1. Modify 24.3.2.3 [iterator.traits] as indicated:

    -4- Explicit or partial specializations of iterator_traits may have a member type iterator_concept that is used to indicate conformance to the iterator concepts (24.3.4 [iterator.concepts]). [Example: To indicate conformance to the input_iterator concept but a lack of conformance to the Cpp17InputIterator requirements (24.3.5.3 [input.iterators]), an iterator_traits specialization might have iterator_concept denote input_iterator_tag and iterator_category denote output_iterator_tag. — end example]

  2. Modify 24.5.5.2 [common.iter.types] as indicated:

    -1- The 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 iterator_traits<I>::iterator_category models derived_from<forward_iterator_tag>; otherwise it denotes input_iterator_tagiterator_traits<I>::iterator_category.

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

  3. Modify 25.7.8.3 [range.filter.iterator] as indicated:

    -3- iterator::iterator_category is defined as follows:

    1. (3.1) — Let C denote the type iterator_traits<iterator_t<V>>::iterator_category.

    2. (3.2) — If C models derived_from<bidirectional_iterator_tag>, then iterator_category denotes bidirectional_iterator_tag.

    3. (3.3) — Otherwise, if C models derived_from<forward_iterator_tag>, then iterator_category denotes forward_iterator_tag.

    4. (3.4) — Otherwise, iterator_category denotes input_iterator_tagC.

  4. Modify 25.7.14.3 [range.join.iterator] as indicated:

    -3- iterator::iterator_category is defined as follows:

    1. (3.1) — Let OUTERC denote iterator_traits<iterator_t<Base>>::iterator_category, and let INNERC denote iterator_traits<iterator_t<range_reference_t<Base>>>::iterator_category.

    2. (3.?) — If OUTERC does not model derived_from<input_iterator_tag>, iterator_category denotes OUTERC.

    3. (3.?) — Otherwise, if INNERC does not model derived_from<input_iterator_tag>, iterator_category denotes INNERC.

    4. (3.2) — Otherwise, iIf ref_is_glvalue is true,

      1. (3.2.1) — If OUTERC and INNERC each model derived_from<bidirectional_iterator_tag>, iterator_category denotes bidirectional_iterator_tag.

      2. (3.2.2) — Otherwise, if OUTERC and INNERC each model derived_from<forward_iterator_tag>, iterator_category denotes forward_iterator_tag.

    5. (3.3) — Otherwise, iterator_category denotes input_iterator_tag.

  5. Modify [range.split.outer] as indicated:

    namespace std::ranges {
      template<class V, class Pattern>
      template<bool Const>
      struct split_view<V, Pattern>::outer_iterator {
      private:
        […]
      public:
        using iterator_concept =
          conditional_t<forward_range<Base>, forward_iterator_tag, input_iterator_tag>;
        using iterator_category = see belowinput_iterator_tag;
        […]
      };
      […]
    }
    

    -?- The typedef-name iterator_category denotes input_iterator_tag if iterator_traits<iterator_t<Base>>::iterator_category models derived_from<input_iterator_tag>, and iterator_traits<iterator_t<Base>>::iterator_category otherwise.

    -1- Many of the following specifications refer to the notional member current of outer_iterator. current is equivalent to current_ if V models forward_range, and parent_->current_ otherwise.

  6. Modify [range.split.inner] as indicated:

    -1- The typedef-name iterator_category denotes forward_iterator_tag if iterator_traits<iterator_t<Base>>::iterator_category models derived_from<forward_iterator_tag>, and input_iterator_tagiterator_traits<iterator_t<Base>>::iterator_category otherwise.

[2020-02-10, Prague]

The issue is out of sync with the current working draft, Daniel provides a synchronized merge.

[2020-02-13, Prague; Priority reduced to 2 after LWG discussion]

[2021-05-18 Resolved by the adoption of P2259R1 at the February 2021 plenary. Status changed: New → Resolved.]

Proposed resolution:

This wording is relative to N4849.

  1. Modify 24.3.2.3 [iterator.traits] as indicated:

    -4- Explicit or partial specializations of iterator_traits may have a member type iterator_concept that is used to indicate conformance to the iterator concepts (24.3.4 [iterator.concepts]). [Example: To indicate conformance to the input_iterator concept but a lack of conformance to the Cpp17InputIterator requirements (24.3.5.3 [input.iterators]), an iterator_traits specialization might have iterator_concept denote input_iterator_tag and iterator_category denote output_iterator_tag. — end example]

  2. Modify 24.5.5.2 [common.iter.types] as indicated:

    -1- The 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 iterator_traits<I>::iterator_category models derived_from<forward_iterator_tag>; otherwise it denotes input_iterator_tagiterator_traits<I>::iterator_category.

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

  3. Modify 25.7.8.3 [range.filter.iterator] as indicated:

    -3- iterator::iterator_category is defined as follows:

    1. (3.1) — Let C denote the type iterator_traits<iterator_t<V>>::iterator_category.

    2. (3.2) — If C models derived_from<bidirectional_iterator_tag>, then iterator_category denotes bidirectional_iterator_tag.

    3. (3.3) — Otherwise, if C models derived_from<forward_iterator_tag>, then iterator_category denotes forward_iterator_tag.

    4. (3.4) — Otherwise, iterator_category denotes C.

  4. Modify 25.7.14.3 [range.join.iterator] as indicated:

    -2- iterator::iterator_category is defined as follows:

    1. (2.1) — Let OUTERC denote iterator_traits<iterator_t<Base>>::iterator_category, and let INNERC denote iterator_traits<iterator_t<range_reference_t<Base>>>::iterator_category.

    2. (2.?) — If OUTERC does not model derived_from<input_iterator_tag>, iterator_category denotes OUTERC.

    3. (2.?) — Otherwise, if INNERC does not model derived_from<input_iterator_tag>, iterator_category denotes INNERC.

    4. (2.2) — Otherwise, iIf ref-is-glvalue is true and OUTERC and INNERC each model derived_from<bidirectional_iterator_tag>, iterator_category denotes bidirectional_iterator_tag.

    5. (2.3) — Otherwise, if ref-is-glvalue is true and OUTERC and INNERC each model derived_from<forward_iterator_tag>, iterator_category denotes forward_iterator_tag.

    6. (2.4) — Otherwise, if OUTERC and INNERC each model derived_from<input_iterator_tag>, iterator_category denotes input_iterator_tag.

    7. (2.5) — Otherwise, iterator_category denotes output_iterator_tag.

  5. Modify [range.split.outer] as indicated:

    [Drafting note: The previous wording change has been adjusted to follow the pattern used in [range.split.inner] p1.]

    namespace std::ranges {
      template<class V, class Pattern>
      template<bool Const>
      struct split_view<V, Pattern>::outer_iterator {
      private:
        […]
      public:
        using iterator_concept =
          conditional_t<forward_range<Base>, forward_iterator_tag, input_iterator_tag>;
        using iterator_category = see belowinput_iterator_tag;
        […]
      };
      […]
    }
    

    -?- The typedef-name iterator_category denotes:

    1. (?.?) — input_iterator_tag if iterator_traits<iterator_t<Base>>::iterator_category models derived_from<input_iterator_tag>;

    2. (?.?) — otherwise, iterator_traits<iterator_t<Base>>::iterator_category.

    -1- Many of the following specifications refer to the notional member current of outer-iterator. current is equivalent to current_ if V models forward_range, and parent_->current_ otherwise.