Why do I need the std::advance, next, prev functions?

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

Author: Abyx, 2016-01-09

2 answers

The std::advance function appeared earlier than the std::next and std::prev functions in the C++ 2003 standard, while the last two functions appeared in the C++ 2011 standard.

The function std::advance 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 is passed to it by reference as the first parameter.

However, as practice has shown, it is very often necessary to create a new iterator that is preceding or following relatively the current iterator. In this case, we had to resort to such pseudo-code (I use the term pseudo-code, since in it I use the keyword auto, 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 addition operation with integer values:

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

Where n is some integer.

For example, consider the task to find 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 function std::advance has the return value type void, and it changes the iterator passed to it as an argument, it is inconvenient to use it with algorithms. Additional declarations and code suggestions are required to invoke some algorithm. For example, this is what calling the std::rotate algorithm for a list using the std::advance {[38 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 quite successful when it comes to computing 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 be a source of errors. For example,

std::advance( middle, -1 );

It is difficult to infer from this sentence whether -1 is a typo or whether this value really expresses the programmer's intention.

Names like prev or next more clearly express the programmer's intentions and make the code more readable.

Therefore, it was proposed to introduce the functions std::prev and std::next in the C++ 2011 standard. Moreover, these functions return an iterator, and therefore they can be embedded in algorithm calls. They do not change the iterators on 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 clogging the code with declarations of intermediate variables, which are only required for calculating the arguments of the algorithms.

Random access iterators can be combined with integer expressions to get a new iterator. For example,

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 make a suggestion with the algorithm call. change.

It will be much better if you use these generalized 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() );

Especially these functions are indispensable when you write template code for an arbitrary type of iterators.

Both the std::next and std::prev functions 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 example,

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

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

It would be logical for the std::advance function to also have a default argument for the second parameter. Then instead of an expression like, for example,

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

It would be easier to write

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

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

I made the following suggestion to include in the function declaration std::advance the default value of the argument equal to 1 for the second parameter functions.

My proposal to change the C++ standard regarding the std::advance function can be found at this link

 17
Author: Vlad from Moscow, 2016-01-09 19:49:47

There are different iterator categories, such as RandomAccessIterator or ForwardIterator.

The iterator category defines which operations are supported by the iterator:

  • RandomAccessIterator can it + n, it += n, ++it, it--, etc.;
  • BidirectionalIterator can only move by one element: ++it, it--;
  • ForwardIterator can only move forward: ++it or it++.

Move by multiple elements

Functions std::advance, std::next and std::prev simplify moving between multiple elements for BidirectionalIterator and simpler iterator categories.

iter = std::next(iter, n); // Эквивалентно iter = iter + n;
iter = std::prev(iter, n); // Эквивалентно iter = iter - n;
std::advance(iter, n); // Эквивалентно iter += n;

For RandomAccessIterator, these functions do not provide any advantages, so it makes no sense to use them instead of the + or += operators.

Move by one element

In the requirements for iterator categories, the operations ++ and -- are defined as follows:

Выражение | Тип
----------+-----
++r       | X&

Thus, the operations ++ and -- are defined only for variables (l-value), and are not defined for r-value values, such as function results. For this reason, an expression like ++f() may not compile:

++std::min_element(first, last) // НЕ РЕКОМЕНДУЕТСЯ: может не скомпилироваться
std::next(std::min_element(first, last)) // OK: next скопирует результат find()
                                         // и вызовет "++" у l-value

Otherwise, there is no reason to use next and prev instead of ++ and --.

 8
Author: Abyx, 2017-04-13 12:53:25