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

### 3733. `ranges::to` misuses `cpp17-input-iterator`

Section: 26.5.7.2 [range.utility.conv.to] Status: Open Submitter: S. B. Tam Opened: 2022-07-10 Last modified: 2022-09-07

Priority: 2

View other active issues in [range.utility.conv.to].

View all other issues in [range.utility.conv.to].

View all issues with Open status.

Discussion:

`ranges::to` uses `cpp17-input-iterator<iterator_t<R>>` to check whether an iterator is a Cpp17InputIterator, which misbehaves if there is a `std::iterator_traits` specialization for that iterator (e.g. if the iterator is a `std::common_iterator`).

>     struct MyContainer {
>         template<class Iter>
>         MyContainer(Iter, Iter);

>         char* begin();
>         char* end();
>     };

>     auto nul_terminated = std::views::take_while([](char ch) { return ch != '\0'; });
>     auto c = nul_terminated("") | std::views::common | std::ranges::to<MyContainer>();  // error

I believe that `ranges::to` should instead use `derived_from<typename iterator_traits<iterator_t<R>>::iterator_category, input_iterator_tag>`, which correctly detects the iterator category of a `std::common_iterator`.

[2022-08-23; Reflector poll]

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

Previous resolution [SUPERSEDED]:

> This wording is relative to N4910.

> 1. Modify 26.5.7.2 [range.utility.conv.to] as indicated:

>    >     template<class C, input_range R, class... Args> requires (!view<C>)
>    >       constexpr C to(R&& r, Args&&... args);

>    > > -1- Returns: An object of type `C` constructed from the elements of `r` in the following manner:

>    > > 1. (1.1) — If `convertible_to<range_reference_t<R>, range_value_t<C>>` is `true`:

>    > > 1. (1.1.1) — If `constructible_from<C, R, Args...>` is `true`:

>    > >    > `C(std::forward<R>(r), std::forward<Args>(args)...)`

>    > > 2. (1.1.2) — Otherwise, if `constructible_from<C, from_range_t, R, Args...>` is `true`:

>    > >    > `C(from_range, std::forward<R>(r), std::forward<Args>(args)...)`

>    > > 3. (1.1.3) — Otherwise, if

>    > > 1. (1.1.3.1) — `common_range<R>` is `true`,

>    > > 2. (1.1.3.2) — `cpp17-input-iteratorderived_from<typename iterator_traits<iterator_t<R>>::iterator_category, input_iterator_tag>` is `true`, and

>    > > 3. (1.1.3.3) — `constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...>` is `true`:

>    > >    > `C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)`

>    > > 4. (1.1.4) — Otherwise, if

>    > > 1. (1.1.4.1) — `constructible_from<C, Args...>` is `true`, and

>    > > 2. (1.1.4.2) — `container-insertable<C, range_reference_t<R>>` is `true`:

>    > >    >     […]

>    > > 2. (1.2) — Otherwise, if `input_range<range_reference_t<R>>` is `true`:

>    > >    >     to<C>(r | views::transform([](auto&& elem) {
>    > >    >       return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
>    > >    >     }), std::forward<Args>(args)...);

>    > > 3. (1.3) — Otherwise, the program is ill-formed.

[2022-08-27; Hewill Kang reopens and suggests a different resolution]

This issue points out that the standard misuses `cpp17-input-iterator` to check whether the iterator meets the requirements of Cpp17InputIterator, and proposes to use `iterator_traits<I>::iterator_category` to check the iterator's category directly, which may lead to the following potential problems:

First, for the range types that model both `common_range` and `input_range`, the expression `iterator_traits<I>::iterator_category` may not be valid, consider

>     #include <ranges>

>     struct I {
>       using difference_type = int;
>       using value_type = int;
>       int operator*() const;
>       I& operator++();
>       void operator++(int);
>       bool operator==(const I&) const;
>       bool operator==(std::default_sentinel_t) const;
>     };

>     int main() {
>       auto r = std::ranges::subrange(I{}, I{});
>       auto v = r | std::ranges::to<std::vector<int>>(0);
>     }

Although `I` can serve as its own sentinel, it does not model `cpp17-input-iterator` since postfix `operator++` returns `void`, which causes `iterator_traits<R>` to be an empty class, making the expression `derived_from<iterator_traits<I>::iterator_category, input_iterator_tag>` ill-formed.

Second, for `common_iterator`, `iterator_traits<I>::iterator_category` does not guarantee a strictly correct iterator category in the current standard.

For example, when the above `I::operator*` returns a non-`copyable` object that can be converted to `int`, this makes `common_iterator<I, default_sentinel_t>` unable to synthesize a C++17-conforming postfix `operator++`, however, `iterator_traits<common_iterator<I, S>>::iterator_category` will still give `input_iterator_tag` even though it's not even a C++17 iterator.

Another example is that for `input_iterator`s with difference type of integer-class type, the difference type of the `common_iterator` wrapped on it is still of the integer-class type, but the `iterator_category` obtained by the `iterator_traits` is `input_iterator_tag`.

The proposed resolution only addresses the first issue since I believe that the problem with `common_iterator` requires a paper.

Proposed resolution:

This wording is relative to N4917.

1. Modify 26.5.7.2 [range.utility.conv.to] as indicated:

   >     template<class C, input_range R, class... Args> requires (!view<C>)
   >       constexpr C to(R&& r, Args&&... args);

   > > -1- Returns: An object of type `C` constructed from the elements of `r` in the following manner:

   > > 1. (1.1) — If `convertible_to<range_reference_t<R>, range_value_t<C>>` is `true`:

   > > 1. (1.1.1) — If `constructible_from<C, R, Args...>` is `true`:

   > >    > `C(std::forward<R>(r), std::forward<Args>(args)...)`

   > > 2. (1.1.2) — Otherwise, if `constructible_from<C, from_range_t, R, Args...>` is `true`:

   > >    > `C(from_range, std::forward<R>(r), std::forward<Args>(args)...)`

   > > 3. (1.1.3) — Otherwise, if

   > > 1. (1.1.3.1) — `common_range<R>` is `true`,

   > > 2. (1.1.3.2) — `cpp17-input-iterator` if the qualified-id `iterator_traits``<iterator_t<R>>``::iterator_category` is `true` valid and denotes a type that models `derived_from<input_iterator_tag>`, and

   > > 3. (1.1.3.3) — `constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...>`:

   > >    > `C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)`

   > > 4. (1.1.4) — Otherwise, if

   > > 1. (1.1.4.1) — `constructible_from<C, Args...>` is `true`, and

   > > 2. (1.1.4.2) — `container-insertable<C, range_reference_t<R>>` is `true`:

   > >    >     […]

   > > 2. (1.2) — Otherwise, if `input_range<range_reference_t<R>>` is `true`:

   > >    >     to<C>(r | views::transform([](auto&& elem) {
   > >    >       return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
   > >    >     }), std::forward<Args>(args)...);

   > > 3. (1.3) — Otherwise, the program is ill-formed.