This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

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

Section: 23.3.2.3 [iterator.traits], 23.5.4.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 23.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 (23.3.4 [iterator.concepts]). [Example: To indicate conformance to the `input_iterator` concept but a lack of conformance to the Cpp17InputIterator requirements (23.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 23.5.4.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 24.7.6.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 24.7.12.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, i If `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 99 [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 99 [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 23.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 (23.3.4 [iterator.concepts]). [Example: To indicate conformance to the `input_iterator` concept but a lack of conformance to the Cpp17InputIterator requirements (23.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 23.5.4.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 24.7.6.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 24.7.12.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, i If `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 99 [range.split.outer] as indicated:

   > [Drafting note: The previous wording change has been adjusted to follow the pattern used in 99 [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.