Why std::next does not accept InputIterator? - c++

ISO C++11 24.3:
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);
// ...
template <class ForwardIterator>
ForwardIterator next
(
ForwardIterator x,
typename std::iterator_traits<ForwardIterator>::difference_type n = 1
);
Why std::next does not accept InputIterators?
One of legal use cases I am thinking about is:
first = find(next(first, x), last, 11); // ...
I have found appropriate DR:
next/prev return an incremented iterator without changing the value of the original iterator. However, even this may invalidate an InputIterator. A ForwardIterator is required to guarantee the 'multipass' property.
But I don't understand how multipass/invalidation is related to that. Using same multipass/invalidation reasoning, we can even ban std::find for InputIterators:
template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value);
There is nothing special about std::next in compare to std::find or std::vector::insert(pos, first, last) which have perfectly legal use cases for InputIterators
Moreover std::next(it, n) can be used in generic code, which operates not only on InputIterators.

In effect, input iterators cannot be usefully copied, because once an input iterator is incremented, any copy left lying around is invalidated.
std::next takes an iterator and returns another iterator which has been advanced n times. You can't do that with an input iterator without invalidating the original iterator, which makes std::next pointless. By constrast, std::advance advances the specified iterator n times, which is fine with an input iterator.
std::next is the iterator generalization of operator+(T*, size_t). std::advance is the iterator generalization of operator+=(T*&, size_t). It may well be that std::advance, like operator+=, should return a reference instead of void.
It's true that there is a similar issue with std::find (and related functions); they, too, will invalidate any copy of the specified input iterators. But it is quite possible that the committee found that issue less serious.

Related

Why can't I use istream_view and std::accumulate to sum up my input?

This program:
#include <ranges>
#include <numeric>
#include <iostream>
int main() {
auto rng = std::ranges::istream_view<int>(std::cin);
std::cout << std::accumulate(std::ranges::begin(rng), std::ranges::end(rng), 0);
}
is supposed to sum up all integers appearing as text on the standard input stream. But - it doesn't compile. I know std::ranges::begin() and std::ranges::end() exist, so what's going on? The compiler tells me it can't find a suitable candidate; why?
From inception up through C++17, everything in <algorithm> is based on iterator pairs: you have one iterator referring to the beginning of a range and one iterator referring to the end of the range, always having the same type.
In C++20, this was generalized. A range is now denoted by an iterator and a sentinel for that iterator - where the sentinel itself need not actually be an iterator of any kind, it just needs to be a type that can compare equal to its corresponding iterator (this is the sentinel_for concept).
C++17 ranges tend to be† valid C++20 ranges, but not necessarily in the opposite direction. One reason is the ability to have a distinct sentinel type, but there are others, which also play into this question (see below).
To go along with the new model, C++20 added a large amount of algorithms into the std::ranges namespace that take an iterator and a sentinel, rather than two iterators. So for instance, while we've always had:
template<class InputIterator, class T>
constexpr InputIterator find(InputIterator first, InputIterator last,
const T& value);
we now also have:
namespace ranges {
template<input_­iterator I, sentinel_­for<I> S, class T, class Proj = identity>
requires indirect_­binary_­predicate<ranges::equal_to, projected<I, Proj>, const T*>
constexpr I find(I first, S last, const T& value, Proj proj = {});
template<input_­range R, class T, class Proj = identity>
requires indirect_­binary_­predicate<ranges::equal_to,
projected<iterator_t<R>, Proj>, const T*>
constexpr borrowed_iterator_t<R>
find(R&& r, const T& value, Proj proj = {});
}
The first overload here takes an iterator/sentinel pair and the second takes a range instead.
While a lot of algorithms added corresponding overloads into std::ranges, the ones in <numeric> were left out. There is a std::accumulate but there is no std::ranges::accumulate. As such, the only version we have available at the moment is one that takes an iterator-pair. Otherwise, you could just write:
auto rng = std::ranges::istream_view<int>(std::cin);
std::cout << std::ranges::accumulate(rng, 0);
Unfortunately, std::ranges::istream_view is one of the new, C++20 ranges whose sentinel type differs from its iterator type, so you cannot pass rng.begin() and rng.end() into std::accumulate either.
This leaves you with two options generally (three, if you include waiting for C++23, which will hopefully have a std::ranges::fold):
Write your own range-based and iterator-sentinel-based algorithms. Which for fold is very easy to do.
Or
There is a utility to wrap a C++20 range into a C++17-compatible one: views::common. So you could this:
auto rng = std::ranges::istream_view<int>(ints) | std::views::common;
std::cout << std::accumulate(rng.begin(), rng.end(), 0);
Except not in this specific case.
istream_view's iterators aren't copyable, and in C++17 all iterators must be. So there isn't really a way to provide C++17-compatible iterators based on istream_view. You need proper C++20-range support. The future std::ranges::fold will support move-only views and move-only iterators, but std::accumulate never can.
Which in this case, just leaves option 1.
†A C++20 iterator needs to be default-constructible, which was not a requirement of C++17 iterators. So a C++17 range with non-default-constructible iterators would not be a valid C++20 range.
The problem is that the end of a C++ range is not, in the general case, an iterator, but rather, a sentinel. A sentinel can have a different type than an iterator and admit fewer operations - as, generally speaking, you mostly need to compare against it to know you've reached the end of the range, and may not be allowed to just work with it like any iterator. For more about this distinction, read:
What's the difference between a sentinel and an end iterator?
Now, standard-library algorithms (including the ones in <numeric>) take pairs of iterators of the same type. In your example:
template< class InputIt, class T >
constexpr T accumulate( InputIt first, InputIt last, T init );
see? InputIt must be the type of both the beginning and end of the range. This can (probably) not even be "fixed" for the istream_view range, because the end of the standard input really isn't an iterator per se. (Although maybe you could bludgeon it into being an iterator and throw exceptions when doing irrelevant things with it.)
So, we would need either a new variant of std::accumulate or an std::ranges::accumulate, which we currently don't have. Or, of course, you could write that yourself, which should not be too difficult.
Edit: One last option, suggested by #RemyLebeau, is to use an std::istream_iterator instead:
std::cout << std::accumulate(
std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(),
0);

Why does std::fill use ForwardIterator, not OutputIterator?

(This question has the same "question template" as Why does std::max_element require a ForwardIterator? but the answer has to be different, because std::fill doesn't return an iterator into the output sequence.)
std::fill is defined to work only when the output range is given by a pair of ForwardIterators. However, the superficially similar std::fill_n works fine with OutputIterator.
Surely the algorithm is simply
template<class OutIt, class T>
void fill(OutIt first, OutIt last, T value)
{
while (first != last) {
*first = value;
++first;
}
}
What about this algorithm requires ForwardIterator? What am I missing?
Output iterators are not comparable. The == and != operators are not defined for output iterators.
You need a forward iterator, because it
satisfies the requirements of an input iterator
which supports EqualityComparable.
With output iterators, std::fill cannot compare first with last:
while (first != last) {
Not supported, for output iterators.
std::fill_n avoids this comparison, it just uses the counter to write to the iterator, so all it needs is an output iterator.
There is no notion of "range" for OutputIterator. Thus you must provide a repetition count. With ForwardIterator you can work over a range of elements.
Those are semantically different things. fill_n is for adding some elements starting from a supplied iterator. fill is for changing a range of elements.
Take inserters (e.g. a back_inserter) for an example. You can insert more elements than there is in the container, so last doesn't even make sense.
OutputIterator gives you a place where you can throw in an object. ForwardIterator objects must exists.
From cppreference:
The only valid use of operator* with an output iterator is on the left of an assignment: operator* may return a proxy object, which defines a member operator= (which may be a template)
This means you basically cannot read from it, which is in the contrast to the ForwardIterator:
A ForwardIterator is an Iterator that can read data from the pointed-to element.

STL fill and forward iterator

According to most C++ references, for instance cplusplus.com, forward iterators are not required to be assignable (I mean, deferenced to an lvalue). However, for several STL algorithms that need to write values, for instance std::fill (also std::generate etc.), the specification uses forward iterator:
template <class ForwardIterator, class T>
void fill (ForwardIterator first, ForwardIterator last, const T& val);
while the equivalent behavior requires lvalue dereference:
template <class ForwardIterator, class T>
void fill (ForwardIterator first, ForwardIterator last, const T& val)
{
while (first != last) {
*first = val;
++first;
}
}
So, it is actually using a mutable forward iterator with a single pass.
Now the questions are:
(1) Why not make it clear that the forward iterators used in these cases are mutable?
(2) Update: I found the following question to be stupid: I temporarily forgot that output iterators do not need to support equality comparison. The above question remains, anyway.
Why use forward iterators instead of output iterators for std::fill, std::generate etc. while they do not actually need multiple passes? (std::copy only needs output iterators, for instance. What's the rationale?)
From the signature
template <class ForwardIterator, class T>
void fill (ForwardIterator first, ForwardIterator last, const T& val);
you cannot infer that ForwardIterator is an iterator described in forward iterator. However, if you read the parameter description, you will find that first and last must be
Forward Iterators to the initial and final positions in a sequence of elements that support being assigned a value of type T.
(emphasis by me). So a forward iterator that fulfills nothing more than what is required of a forward iterator is not a valid argument.
It doesn't seem terribly strange to me, given that the specification for fill is that the (dereferenced) iterator be assignable from T. An output iterator won't suffice because it's not comparable to determine the range end, so a forward_iterator with requirements was chosen.
You'll note that fill_n does use output iterators because no iterator comparison is needed to determine the end of the sequence to fill.

What's the difference between std::advance and std::next?

Is there more beyond advance takes negative numbers?
std::advance
modifies its argument
returns nothing
works on input iterators or better (or bi-directional iterators if a negative distance is given)
std::next
leaves its argument unmodified
returns a copy of the argument, advanced by the specified amount
works on forward iterators or better (or bi-directional iterators if a negative distance is given))
Perhaps the biggest practical difference is that std::next() is only available from C++11.
std::next() will advance by one by default, whereas std::advance() requires a distance.
And then there are the return values:
std::advance(): (none) (the iterator passed in is modified)
std::next(): The n th successor.
std::next() takes negative numbers just like std::advance, and in that case requires that the iterator must be bidirectional. std::prev() would be more readable when the intent is specifically to move backwards.
std::advance
The function advance() increments the position of an iterator passed as the argument. Thus, the function lets the iterator step forward (or backward) more than one element:
#include <iterator>
void advance (InputIterator& pos, Dist n)
Lets the input iterator pos step n elements forward (or backward).
For bidirectional and random-access iterators, n may be negative to step backward.
Dist is a template type. Normally, it must be an integral type because operations such as <, ++, --, and comparisons with 0 are
called.
Note that advance() does not check whether it crosses the end() of a sequence (it can’t check because iterators in general do not know the
containers on which they operate). Thus, calling this function might
result in undefined behavior because calling operator ++ for the end
of a sequence is not defined.
std::next(and std::prev new in C++11)
#include <iterator>
ForwardIterator next (ForwardIterator pos)
ForwardIterator next (ForwardIterator pos, Dist n)
Yields the position the forward iterator pos would have if moved forward 1 or n positions.
For bidirectional and random-access iterators, n may be negative to yield previous ositions.
Dist is type std::iterator_traits::difference_type.
Calls advance(pos,n) for an internal temporary object.
Note that next() does not check whether it crosses the end() of a sequence. Thus, it is up to the caller to ensure that the result is
valid.
cite from The C++ Standard Library Second Edition
They're pretty much the same, except that std::next returns a copy and std::advance modifies its argument. Note that the standard requires std::next to behave like std::advance:
24.4.4 Iterator operations [iterator.operations]
template <class InputIterator, class Distance>
void advance(InputIterator& i [remark: reference], Distance n);
2. Requires: n shall be negative only for bidirectional and random access iterators
3. Effects: Increments (or decrements for negative n) iterator reference i by n.
[...]
template <class ForwardIterator>
ForwardIterator next(ForwardIterator x, [remark: copy]
typename std::iterator_traits<ForwardIterator>::difference_type n = 1);
6. Effects: Equivalent to advance(x, n); return x;
Note that both actually support negative values if the iterator is an input iterator. Also note that std::next requires the iterator to meet the conditions of an ForwardIterator, while std::advance only needs an Input Iterator (if you don't use negative distances).

Traversing through a set from a particular index to a particular index

I have an iterator. Say I need to traverse the set not from the beginning but from some particular point. Also it is quite difficult for me to get the values stored in the sets as they are pointers. So how to i modify my code to traverse through my sets from a point that is not the begining. ?
Here is the code:
for(iter=make.at(level).begin();iter!=make.at(level).end();iter++)
{
Function(*iter);
}
Using this gives an error:
for(iter=make.at(level).begin()+10;iter!=make.at(level).end();iter++)
{
Function(*iter);
}
There are different types of iterators: ForwardIterator, BidirectionalIterator, and RandomAccessIterator.
ForwardIterator allows you to move forward only, using the increment operator. BidirectionalIterator allows both directions. RandomAccessIterator allows any advancement, including operator+ and operator-.
The one you're thinking in terms of is RandomAccessIterator, like the one found in std::vector. What std::set uses, though, is the BidirectionalIterator. That means you can only increment and decrement.
Therefore, you need to make your iterator outside of your loop and advance it forward ten times. To make it simple, std::advance does this, and has different compatibility for BidirectionalIterator, as well as ForwardIterator (linear time because of only one increment at a time), and RandomAccessIterator (constant time due to operator+).
std::set<T>::iterator iter = make.at(level).begin(); //more C++03 way
auto iter = std::begin (make.at(level)); //more C++11 way
std::advance (iter, 10); //start iterator 10 elements past beginning
for (...)