c++ – Why are std :: advance, next, prev functions needed?

Question:

The C ++ standard library has std::advance , std::next , std::prev functions.
Why are they needed and when should they be used?

Answer:

The std::advance function appeared prior to the std::next and std::prev functions in the C ++ 2003 standard, while the latter two functions appeared in the C++ 2011 standard.

The std::advance function has the following declaration

template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);

As you can see from the declaration, the function changes the iterator that was passed to it by reference as the first parameter.

However, as practice has shown, it is very often required to create a new iterator, which is preceding or following relative to the current iterator. In this case, I had to resort to such pseudo-code (I use the term pseudo-code, since I use the auto keyword in it, which in the C ++ 2003 standard did not yet have the meaning that it has in the C ++ 2011 standard), since in general, iterators, with the exception of random access iterators, did not have an integer addition operation:

auto next = current;
advance( next, n );

where n is some integer.

For example, consider the problem of finding the maximum element in the second half of the elements of some list

#include <list>
#include <algorithm>
#include <iterator>

// ...

std::list<int> lst;

// инициализация списка некоторыми значениями

std::list<int>::iterator it = lst.begin();
std::advance( it, lst.size() / 2 );

it = std::max_element( it, lst.end() );

Since the std::advance function has a void return type, and it changes the iterator passed to it as an argument, it is inconvenient to use with algorithms. Additional declarations and code suggestions are required to invoke any algorithm. For example, this is what calling the std::rotate algorithm on a list using the std::advance function might look like.

#include <list>
#include <algorithm>
#include <iterator>

// ...

std::list<int> lst;

// инициализация списка некоторыми значениями

std::list<int>::iterator middle = lst.begin();
std::advance( middle, lst.size() / 2 );
std::rotate( lst.begin(), middle, lst.end() );

In addition, the word advance itself is not entirely good when it comes to evaluating iterators that precede a given iterator. In this case, you need to specify a negative value for the second argument of the function, which can become a source of errors. For instance,

std::advance( middle, -1 );

It is difficult to deduce from this sentence whether -1 typo or whether this value really expresses the intent of the programmer.

Names such as prev or next express the intent of the programmer more clearly and make the code more readable.

Therefore, it was proposed to introduce the std::prev and std::next functions into the C ++ 2011 standard. Moreover, these functions return an iterator, and therefore they can be embedded in algorithm calls. They do not modify the iterators on the basis of which the functions return new iterators.

The previous example of calling the std::rotate algorithm for a list can now be written in one line using these functions

#include <list>
#include <algorithm>
#include <iterator>

// ...

std::list<int> lst;

// инициализация списка некоторыми значениями

std::rotate( lst.begin(), std::next( lst.begin(), lst.size() / 2 ), lst.end() );

That is, you can get new iterators or expressions with iterators "on the fly" without cluttering the code with declarations of intermediate variables that are required only to calculate the arguments of the algorithms.

Random access iterators can be added with integer expressions to create a new iterator. For instance,

std::vector<int> sequence = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

std::rotate( sequence.begin(), sequence.begin() + sequence.size() / 2, sequence.end() );

However, this code is not flexible. If for some reason you want to use another container that does not have random access iterators, then you will have to change the statement with the call to the algorithm.

It will be much better if you use these generic functions even for random access iterators

std::vector<int> sequence = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

std::rotate( sequence.begin(), std::next( sequence.begin(), sequence.size() / 2 ), sequence.end() );

These functions are especially useful when you are writing boilerplate code for arbitrary iterator types.

Both std::next and std::prev have a default argument for the second parameter:

template <class ForwardIterator>
ForwardIterator next(ForwardIterator x,
typename std::iterator_traits<ForwardIterator>::difference_type n = 1);


template <class BidirectionalIterator>
BidirectionalIterator prev(BidirectionalIterator x,
typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1);

Therefore, these functions are very convenient to use when you need to get the next or previous iterator. For instance,

std::vector<int> v = { /* некоторые значения */ };

auto after_first = std::next( v.begin() );
auto before_last = std::prev( v.end() );

It would make sense for std::advance also have a default argument for the second parameter. Then instead of an expression such as

auto it = v.begin();
std::advance( it, 1 );

could be written more simply

auto it = v.begin();
std::advance( it );

And the word advance in this case would correspond to its immediate meaning.

I made such a proposal to include in the std::advance function declaration a default argument value of 1 for the second function parameter.

My current proposal for changing the C ++ standard regarding the std::advance function can be found at this link

Scroll to Top