[Лучший ответ, к сожалению, был удален модератором, потому что это был ответ только по ссылке. Я понимаю, почему ответы только на ссылки не рекомендуются; однако его удаление лишило будущих искателей очень полезной информации. Ссылка оставалась стабильной в течение более семи лет и продолжает работать на момент написания этой статьи.]
Я настоятельно рекомендую оригинальную статью доктора Добба в журнале Мэтта Остерна, озаглавленную «Стандартный библиотекарь: определение итераторов и константных итераторов» , январь 2001 г. Если эта ссылка испортится, теперь, когда у доктора Добба есть перестал работать, он также доступен здесь .
Чтобы не допустить удаления этого замещающего ответа, я кратко изложу решение.
Идея состоит в том, чтобы реализовать итератор один раз в качестве шаблона, который принимает дополнительный параметр шаблона, логическое значение, указывающее, является ли это константной версией. Везде, где реализация отличается от константной и неконстантной версий, вы используете шаблонный механизм для выбора правильного кода. Механизм Мэтта Остерна назывался choose
. Выглядело это так:
template <bool flag, class IsTrue, class IsFalse>
struct choose;
template <class IsTrue, class IsFalse>
struct choose<true, IsTrue, IsFalse> {
typedef IsTrue type;
};
template <class IsTrue, class IsFalse>
struct choose<false, IsTrue, IsFalse> {
typedef IsFalse type;
};
Если бы у вас были отдельные реализации для константных и неконстантных итераторов, тогда реализация const включала бы определения типа, подобные этому:
typedef const T &reference;
typedef const T *pointer;
и неконстантная реализация будет иметь:
typedef T &reference;
typedef T *pointer;
Но с choose
вы можете иметь одну реализацию, которая выбирает на основе дополнительного параметра шаблона:
typedef typename choose<is_const, const T &, T &>::type reference;
typedef typename choose<is_const, const T *, T *>::type pointer;
При использовании typedef для базовых типов все методы итератора могут иметь одинаковую реализацию. См. полный пример Мэтта Остерна .