c++iteratorstandardsstd-rangesc++26

Why is `iterator_category` deleted in `std::views::concat::iterator` if it's a pure input iterator?


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:


The relevant cppref link is here.


Solution

  • 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.