4

In the C++26-adopted proposal p2542, i.e. std::views::concat, there is a confusing statement:

The member typedef-name iterator_category is defined if and only if all-forward<Const, Views...> is modeled.

That is to say:

  • If concat_view<Views...> is finally a pure input range, then its iterator type won't contain an inner type iterator_category; otherwise,
  • Its iterator type will contain an inner type iterator_category as usual.

What I cannot understand are:

  • Why does C++26 explicitly prohibit the iterator type to have iterator_category if it's a pure input iterator?
  • What bad consequences would follow without this restriction?

The relevant cppref link is here.

3
  • An input iterator can iterate only in one pass. A view needs to be able to iterate in more than one pass. Forward, bidirectional and random access iterators are capable of making multiple passes. Commented Aug 6 at 10:27
  • "A view needs to be able to iterate in more than one pass"? No. For example, std::views::istream<int> is a view, but cannot iterate more than once. Commented Aug 6 at 10:32
  • Another example is just the std::views::concat, when there is an input range in the underlying ranges, it is an input range, so that it cannot be iterated more than once. Commented Aug 6 at 23:01

2 Answers 2

10

std::views::concat::iterator is not the only iterator not defining iterator_category. In fact, there are plenty of them and some motivations can be found in Repairing input range adaptors and counted_iterator:

1. Abstract

This paper proposes fixes for several issues with iterator_category for range and iterator adaptors. This resolves [LWG3283], [LWG3289], and [LWG3408].

2. The problem with iterator_category

This code does not compile:

std::vector<int> vec = {42};
auto r = vec | std::views::transform([](int c) { return std::views::single(c);})
             | std::views::join
             | std::views::filter([](int c) { return c > 0; });
r.begin();

Not because we are breaking any concept requirements:

  • the transform produces a range of views of int;
  • the join takes that and produce an input range of int;
  • the filter should then produce another input range of int… in theory.

The problem is that join_view’s iterator for this case has a postfix operator++ that returns void, making it not a valid C++17 iterator at all - even C++17 output iterators require *i++ to be valid. In turn, that means that iterator_traits<join_view::iterator> is entirely empty, and filter_view cannot cope with that as currently specified, because it expects iterator_category to be always present (24.7.5.3 [range.filter.iterator]).

[LWG3283] and [LWG3289] were discussed at length during during the Belfast and Prague meetings. LWG was aware that as specified the range adaptors do not work with non-C++17 iterators due to these issues. However, I do not believe that it was clear to LWG that this impacts not just the one move-only iterator we have specified in the standard library, but also virtually every range adaptor with its own iterator type when used in conditions that produce an input range.

2.1. The fix

We shouldn’t (and can’t) change postfix increment on the adaptor’s iterators. There’s nothing we can meaningfully return from operator++ for arbitrary input iterators, especially if we are trying to adapt them. This was discussed in §3.1.2 of [[P0541R1] and there is no need to rehash that discussion here.

These iterators are, then, not C++17 iterators at all, [...]

This paper then goes on to describe the fix for counted_iterator but the reasoning is valid for many iterators, so rather than defining an iterator_category that doesn't really match the capabilities of the specific iterator and risk having programs relying on those capabilities take the wrong decisions, it's not defined at all.

Sign up to request clarification or add additional context in comments.

Comments

3

The post-increment of concat_view::iterator (operator++(int)), returns its copy only if all underlying ranges are forward ranges; otherwise, it returns void.

The C++98/C++17 iterator system requires that for any iterator it, *it++ must be dereferencable, which is not possible for void.

Therefore, in this case, there is no reason to provide an iterator_category for concat_view::iterator since it is not a C++98/C++17 iterator.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.