Уменьшение конечного итератора

Я читал сегодня о том, как для контейнеров, поддерживающих двунаправленную итерацию, этот фрагмент кода действителен:

Collection c(10, 10);
auto last = --c.end();
*last;

это заставило меня задуматься, требуется ли при отправке пары двунаправленных итераторов [beg, end) алгоритму в STL, который определен --end? Если да, то должен ли результат быть разыменован?

ie

void algo(T beg, T end){
    //...
    auto iter = --end;
    //...
    *iter;
} 

4 ответов


если алгоритм требует диапазона, определенного двунаправленными итераторами first и last, потом --last должен быть действительным при тех же условиях, что ++first нет -- а именно, что диапазон не пуст. Диапазон пуст, если и только если first == last.

если диапазон не пуст, то --last возвращает итератор, который ссылается на последний элемент в диапазоне, так *--last действительно также должно быть действительным.

тем не менее, их не так много стандартные алгоритмы, которые требуют конкретно двунаправленного итератора (и не требуют произвольного доступа). prev, copy_backward, move_backward, reverse, reverse_copy, stable_partition, inplace_merge, [prev|next]_permutation.

если вы посмотрите, что некоторые из них делают, вы должны увидеть, что обычно алгоритм уменьшает итератор конца диапазона и разыменовывает результат.

как говорит Джеймс, для контейнеров функция end() возвращает итератор по стоимости. Нет общее требование, что для итераторов, что --x должно быть хорошо сформированное выражение, когда x является rvalue типа. Например, указатели являются двунаправленными итераторами, а функция объявлена как int *foo(); возвращает указатель по значению и --foo() не является хорошо сформированным выражением. Так получилось, что для контейнеров, которые вы рассмотрели в своей реализации,end() возвращает тип класса, который имеет operator-- определяется как функция-член, и поэтому код компилируется. Он также работает поскольку контейнер не пуст.

имейте в виду, что есть разница в этом отношении между:

auto last = --c.end();

и

auto last = c.end();
--last;

первый уменьшает значение rvalue, тогда как последний уменьшает значение lvalue.


Вы читаете неправильно. Выражение --c.end() не уполномочен. Если итератор не является, по крайней мере, двунаправленным, он, по сути, явно запрещено и требует ошибки компилятора. Если коллекция пуста, это неопределенное поведение. И во всех остальных случаях это сработает если он компилирует, но нет никакой гарантии, что он будет компилироваться. Это не удалось для компиляции со многими ранними реализациями std::vector, для пример, где итератор был просто typedef для указателя. (В факт, Я думаю формально, что это неопределенное поведение во всех случаях, так как вы нарушаете ограничение на шаблонную реализацию. В практика, однако, вы получите то, что я только что описал.)

возможно, потому что это не гарантировано, хорошая реализация приведет его систематически не компилировать. По разным причинам большинство этого не делает. Не спрашивайте меня, почему, потому что это невероятно просто, чтобы заставить его потерпеть неудачу систематически: просто сделайте operator-- на итераторе a бесплатно функция, а не член.

EDIT (дополнительная информация):

тот факт, что это не требуется, вероятно, большая часть мотивация std::next и std::prev в C++11. Конечно, в любом случае, в каждом проекте, над которым я работал, они были. Правильный путь к написать это:

prev( c.end() );

и, конечно же, ограничения, которые итератор будет двунаправленным или лучше, и чтобы контейнер не был пустым, все-таки подержите.


каждый алгоритм скажет вам, какой тип итератора он требует. Когда вызывается двунаправленный итератор, то, естественно,ему нужно будет поддерживать уменьшение.

ли --end возможно, зависит от того end == beg.


это требуется только для алгоритмов, требующих двунаправленных итераторов.