ОК, у нас есть два похожих, но разных понятия. Итак, давайте выложим их.
Но сначала мне нужно провести различие между именованными требованиями C ++ - pre-20 и реальными языковыми концепциями, созданными для Ranges TS и включенными в C ++ 20. Их обоих называют «понятиями», но они определены по-разному . Таким образом, когда я говорю о концепте с строчными буквами c, я имею в виду требования до C ++ 20. Когда я говорю о Concept-with-a-captial-C, я имею в виду C ++ 20.
Итераторы прокси
Прокси-итераторы - это итераторы, в которых reference
- это не value_type&
, а какой-то другой тип, который ведет себя как ссылка на value_type
. В этом случае *it
возвращает значение для этого reference
.
Концепция InputIterator не предъявляет никаких требований к reference
, за исключением того, что она конвертируется в value_type
. Однако концепция ForwardIterator делает явное утверждение, что «reference
является ссылкой на T
».
Следовательно, прокси-итератор не может соответствовать концепции ForwardIterator. Но может все еще быть InputIterator. Таким образом, вы можете безопасно передавать прокси-итератор любой функции, которая требует только InputIterators.
Итак, проблема с итераторами vector<bool>
не в том, что они являются итераторами-посредниками. Они обещают, что они соответствуют концепции RandomAccessIterator (хотя и с использованием соответствующего тега), когда они на самом деле являются только InputIterators и OutputIterators.
Предложение Ranges (в основном), принятое в C ++ 20, вносит изменения в Концепции итераторов, которые позволяют прокси-итераторам использовать для всех итераторов. Таким образом, в диапазонах vector<bool>::iterator
действительно соответствует концепции RandomAccessIterator. Поэтому, если у вас есть код, написанный против концепций Ranges, вы можете использовать прокси-итераторы всех видов.
Это очень полезно для таких вещей, как подсчет диапазонов. Вы можете иметь reference
и value_type
одного типа, так что вы в любом случае имеете дело с целыми числами.
И, конечно, если у вас есть контроль над кодом, потребляющим итератор, вы можете заставить его делать все, что захотите, при условии, что вы не нарушаете концепцию, против которой написан ваш итератор.
Копирование итераторов
Скрывающие итераторы - это итераторы, где reference_type
- это (прямо или косвенно) ссылка на объект, сохраненный в итераторе. Поэтому, если вы сделаете копию итератора, копия вернет ссылку на объект, отличный от оригинала, даже если они ссылаются на один и тот же элемент. И когда вы увеличиваете итератор, предыдущие ссылки больше не действительны.
Стерирующие итераторы обычно реализуются, потому что вычисление значения, которое вы хотите вернуть, стоит дорого. Возможно, это будет связано с выделением памяти (например, path::iterator
), или, возможно, это будет связано с возможно сложной операцией, которая должна быть выполнена только один раз (например, regex_iterator
). Так что вы хотите делать это только тогда, когда это необходимо.
Одна из основ ForwardIterator как концепции (или концепции) заключается в том, что диапазон этих итераторов представляет диапазон значений, которые существуют независимо их итераторов. Это разрешает многопроходную работу, но делает полезными и другие вещи. Вы можете хранить ссылки на элементы в диапазоне, а затем выполнять итерации в другом месте.
Если вам нужен итератор для ForwardIterator или выше, вы должны никогда не делать его итератором. Конечно, стандартная библиотека C ++ не всегда соответствует сама себе. Но обычно он вызывает свои несоответствия.
path::iterator
является итератором хранения. Стандарт говорит, что это двунаправленный итератор; однако он также дает этому типу исключение из правила сохранения ссылок / указателей. Это означает, что вы не можете передать path::iterator
любому коду, который может полагаться на это правило сохранения.
Так вот, это не значит, что вы ничего не можете передать. Любой алгоритм, который требует только InputIterator, сможет использовать такой итератор, поскольку такой код не может полагаться на это правило. И, конечно, может быть использован любой код, который вы пишете или который конкретно указывает в своей документации, что он не полагается на это правило. Но нет никакой гарантии, что вы можете использовать reverse_iterator
для него, даже если он говорит, что это двунаправленный интерфейс.
regex_iterator
с еще хуже в этом отношении. Говорят, что они являются ForwardIterators на основе своего тега, но в стандарте никогда не говорится, что они на самом деле являются ForwardIterators (в отличие от path::iterator
). И их спецификация как имеющая reference
фактическую ссылку на объект-член делает невозможным для них быть настоящими ForwardIterators.
Обратите внимание, что я не делал различий между концепцией до C ++ 20 и концепцией Ranges. Это потому, что концепция ForwardIterator по-прежнему запрещает прятать итераторы. Это по замыслу.
Использование
Теперь, очевидно, вы можете делать все, что захотите, в своем коде. Но код, который вы не контролируете, будет принадлежать его владельцам. Они будут писать против старых концепций, новых концепций или какой-либо другой концепции или требований, которые они определяют. Поэтому ваши итераторы должны быть в состоянии быть совместимыми с их потребностями.
Алгоритмы, добавленные в добавление Ranges, используют новые концепции, поэтому вы всегда можете положиться на них при работе с прокси-итераторами. Однако, насколько я понимаю, концепции диапазона не перенесены в более старые алгоритмы.
Лично я бы посоветовал полностью избежать реализации итераторов. Обеспечивая полную поддержку прокси-итераторов, большинство сохраняющих итераторов можно переписать так, чтобы они возвращали значения , а не ссылки на объекты.
Например, если бы существовал тип path_view
, path::iterator
мог бы вернуть его вместо полноценного path
. Таким образом, если вы хотите сделать дорогую операцию копирования, вы можете. Точно так же regex_iterator
s могли бы вернуть копии объекта сопоставления. Новые концепции позволяют работать таким образом, поддерживая прокси-итераторы.
Теперь итераторы-хранилища полезны для кеширования; итераторы могут кэшировать свои результаты, так что повторное использование *it
делает дорогую операцию только один раз. Но помните, что проблема с засыпкой итераторов: возвращение ссылки на их содержимое. Вам не нужно , чтобы сделать это только для того, чтобы получить кеширование. Вы можете кэшировать результаты в optional<T>
(который вы аннулируете, когда итератор включен / уменьшен). Таким образом, вы все еще можете вернуть значение. Может потребоваться дополнительная копия, но reference
не должен быть сложным типом.
Конечно, все это означает, что auto &val = *it;
больше не является юридическим кодом. Тем не менее, auto &&val = *it;
всегда будет работать. На самом деле это большая часть версии итераторов Range TS.