Впрочем, некомпилируемое преобразование все же может откомпилироваться, если итераторы относятся к контейнеру vector
или string
. Это объясняется тем, что в реализациях данных контейнеров в качестве итераторов обычно используются указатели. В этих реализациях vector
является определением типа для T*, vector
— для const T*
, string::iterator
— для char*
, а string::const_iterator
— для const char*
. В реализациях данных контейнеров преобразование const_iterator
в iterator
вызовом const_cast
компилируется и даже правильно работает, поскольку оно преобразует const T*
в T*
. Впрочем, даже в этих реализациях reverse_iterator
и const_reverse_iterator
являются полноценными классами, поэтому const_cast
не позволяет преобразовать const_reverse_iterator
в reverse_iterator
. Кроме того, как объясняется в совете 50, даже реализации, в которых итераторы контейнеров vector
и string
представлены указателями, могут использовать это представление лишь при компиляции окончательной (release) версии. Все перечисленные факторы приводят к мысли, что преобразование const
-итераторов в итераторы не рекомендуется и для контейнеров vector
и string
, поскольку переносимость такого решения будет сомнительной.
Если у вас имеется доступ к контейнеру, от которого был взят const_iterator
, существует безопасный, переносимый способ получения соответствующего типа iterator
без нарушения системы типов. Ниже приведена основная часть этого решения (возможно, перед компиляцией потребуется внести небольшие изменения):
typedef deque
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
IntDeque d;
ConstIter ci;
… // Присвоить ci ссылку на d
Iter i(d.begin); // Инициализировать i значением d.begin
advance(i, distance(i, ci)); // Переместить i в позицию ci
Решение выглядит настолько простым и прямолинейным, что это невольно вызывает подозрения. Чтобы получить iterator
, указывающий на тот же элемент контейнера, что и const_iterator
, мы создаем новый iterator
в начале контейнера и перемещаем его вперед до тех пор, пока он не удалится на то же расстояние, что и const_iterator
! Задачу упрощают шаблоны функций advance
и distance
, объявленные в
. Distance
возвращает расстояние между двумя итераторами в одном контейнере, a advance
перемещает итератор на заданное расстояние. Когда итераторы i
и ci
относятся к одному контейнеру, выражение advance(i, distance(i, ci))
переводит их в одну позицию контейнера.
Все хорошо, если бы этот вариант компилировался… но этого не происходит. Чтобы понять причины, рассмотрим объявление distance
:
template
typename iterator_traits
distance(InputIterator first, InputIterator last);
Не обращайте внимания на то, что тип возвращаемого значения состоит из 56 символов и содержит упоминания зависимых типов (таких как differenceype). Вместо этого проанализируем использование параметра-типа InputIterator
:
template
typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last);
При вызове distance
компилятор должен определить тип, представленный InputIterator
, для чего он анализирует аргументы, переданные при вызове. Еще раз посмотрим на вызов distance
в приведенном выше коде:
advance(i, distance(i,ci)); // Переместить i в позицию ci