ranges::to
misuses cpp17-input-iterator
Section: 25.5.7.2 [range.utility.conv.to] Status: C++23 Submitter: S. B. Tam Opened: 2022-07-10 Last modified: 2024-01-29
Priority: 2
View other active issues in [range.utility.conv.to].
View all other issues in [range.utility.conv.to].
View all issues with C++23 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.
Modify 25.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 ofr
in the following manner:
(1.1) — If
convertible_to<range_reference_t<R>, range_value_t<C>>
istrue
:
(1.1.1) — If
constructible_from<C, R, Args...>
istrue
:C(std::forward<R>(r), std::forward<Args>(args)...)
(1.1.2) — Otherwise, if
constructible_from<C, from_range_t, R, Args...>
istrue
:C(from_range, std::forward<R>(r), std::forward<Args>(args)...)
(1.1.3) — Otherwise, if
(1.1.3.1) —
common_range<R>
istrue
,(1.1.3.2) —
is
cpp17-input-iteratorderived_from<typename iterator_traits<iterator_t<R>>::iterator_category, input_iterator_tag>true
, and(1.1.3.3) —
constructible_from<C, iterator_t<R>, sentinel_t<R>, Args...>
istrue
:C(ranges::begin(r), ranges::end(r), std::forward<Args>(args)...)
(1.1.4) — Otherwise, if
(1.1.4.1) —
constructible_from<C, Args...>
istrue
, and(1.1.4.2) —
container-insertable<C, range_reference_t<R>>
istrue
:[…](1.2) — Otherwise, if
input_range<range_reference_t<R>>
istrue
:to<C>(r | views::transform([](auto&& elem) { return to<range_value_t<C>>(std::forward<decltype(elem)>(elem)); }), std::forward<Args>(args)...);(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:
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.
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.
[2023-01-11; LWG telecon]
Set status to Tentatively Ready (poll results F6/A0/N1)
[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4917.
Modify 25.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 ofr
in the following manner:
(1.1) — If
convertible_to<range_reference_t<R>, range_value_t<C>>
istrue
:
(1.1.1) — If
constructible_from<C, R, Args...>
istrue
:C(std::forward<R>(r), std::forward<Args>(args)...)
(1.1.2) — Otherwise, if
constructible_from<C, from_range_t, R, Args...>
istrue
:C(from_range, std::forward<R>(r), std::forward<Args>(args)...)
(1.1.3) — Otherwise, if
(1.1.3.1) —
common_range<R>
istrue
,(1.1.3.2) —
if the qualified-idcpp17-input-iterator
iterator_traits
<iterator_t<R>>
::iterator_category
isvalid and denotes a type that modelstrue
derived_from<input_iterator_tag>
, and(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)...)
(1.1.4) — Otherwise, if
(1.1.4.1) —
constructible_from<C, Args...>
istrue
, and(1.1.4.2) —
container-insertable<C, range_reference_t<R>>
istrue
:[…](1.2) — Otherwise, if
input_range<range_reference_t<R>>
istrue
:to<C>(r | views::transform([](auto&& elem) { return to<range_value_t<C>>(std::forward<decltype(elem)>(elem)); }), std::forward<Args>(args)...);(1.3) — Otherwise, the program is ill-formed.