Делаю STL-совместимый самодельный контейнер, который обертка над стандартными контейнерами.
Соответственно, потребовались iterator и const_iterator.
Вопрос про то, как лучше/удобнее реализовать эту парочку, с минимумом копипасты и односторонней заменяемости от iterator к const_iterator.
Что-то я не нашел гайдлайнов для такой задачи...
Очевидно, надо сделать у const_iterator конструктор и присваивание от iterator:
class const_iterator
{
...
const_iterator(const iterator&);
const_iterator& operator=(const iterator&);
};
А у iterator оператор преобразования к const_iterator:
class iterator
{
...
operator const_iterator() const;
};
Но этого недостаточно для парных операций, где первый аргумент iterator.
Пришлось добавлять:
Как-то громоздко получается и нет полной уверенности, что еще чего-то не учел.
Посмотрел, как решена задача в MS реализации STL для вектора.
Там iterator наследуют от const_iterator.
Это удобно, но в моем случае не подходит, т.к. члены данных в моих iterator и const_iterator разного типа (итераторы нижележащих контейнеров).
Здравствуйте, qaz77, Вы писали:
Q>Делаю STL-совместимый самодельный контейнер, который обертка над стандартными контейнерами. Q>Соответственно, потребовались iterator и const_iterator. Q>Вопрос про то, как лучше/удобнее реализовать эту парочку, с минимумом копипасты и односторонней заменяемости от iterator к const_iterator. Q>Что-то я не нашел гайдлайнов для такой задачи...
Первый класс дает больше возможностей, второй — более прост в обращении. Идея же в обоих случаях одинаковая — ты определяешь один общий шаблонный класс, а константная и неконстантная версии итераторов являются обычными алиасами этого шаблона, параметризованного чуть по-разному. Interoperability достигается очень просто: один шаблонный конструктор и шаблонные операторы сравнения. Все! Не нужно наследования. Не нужно операторов присваивания. Ни в коем случае не делай операторов неявного преобразования, если не хочешь проблем, вызванных неоднозначностью.
Ознакомься внимательно с Tutorial Example из iterator facade, чтобы в деталях понять идею. Особое внимание обрати на пункт "Interoperability". Ну а что именно тебе больше подходит — facade или adaptor — уже разберешься самостоятельно.
Удачи!
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, qaz77, Вы писали:
Q>Делаю STL-совместимый самодельный контейнер, который обертка над стандартными контейнерами. Q>Соответственно, потребовались iterator и const_iterator. Q>Вопрос про то, как лучше/удобнее реализовать эту парочку, с минимумом копипасты и односторонней заменяемости от iterator к const_iterator. Q>Что-то я не нашел гайдлайнов для такой задачи...
Я вот здесь набросал простейший proof-of-concept контейнера-враппера для std::vector и враппера для итераторов с использованием boost::iterator_adaptor. Как видишь, все, что потребовалось — это один единственный дополнительный конструктор. Даже операторы сравнения и вычитания не пришлось определять самому:
Здравствуйте, rg45, Вы писали:
R>Я вот здесь набросал простейший proof-of-concept контейнера-враппера для std::vector и враппера для итераторов с использованием boost::iterator_adaptor. Как видишь, все, что потребовалось — это один единственный дополнительный конструктор. Даже операторы сравнения и вычитания не пришлось определять самому:
Спасибо, буду разбираться.
К сожалению, проект у меня без буста...
Здравствуйте, rg45, Вы писали:
R>Но можно позаимствовать идею. Собственная версия, скорее всего, будет проще бустовой благодаря своей более узкой применимости.
Да, в это и подразумевал под "разбираться".
Просто мой вариант показался слишком громоздким, хотя работает и все тесты проходит.
Здравствуйте, qaz77, Вы писали:
Q>Да, в это и подразумевал под "разбираться". Q>Просто мой вариант показался слишком громоздким, хотя работает и все тесты проходит.
А чтоб легче было разбираться, вот тебе то же самое, только без буста, в ОЧЕНЬ-ОЧЕНЬ упрощенном виде. По крайней мере, на те вопросы, которые ты задавал в топике, этот пример отвечает:
Типы членов данных можно засунуть в параметры шаблона обобщенного итератора.
Если теперь сравнения, разницу и конструктор сделать шаблонными членами, то асимметрия конст/не-конст будет достигаться возможностью копирования соответствующих членов данных.
Кажется должно сработать. Спасибо за подсказки.
Здравствуйте, qaz77, Вы писали:
Q>У меня не такой случай, когда надо адаптировать один готовый итератор. Q>Реализация инкремента, разницы и т.д. своя.
Q>Упрощенно есть std::vector<std::vector<T>>, надо итерироваться с посещением вложенных векторов.
В принципе, все то же самое, что и раньше, только шаблонный класс итератора теперь инкапсулирует два итератора и параметризуется двумя параметрами (компилятом не проверял):
template <typename OuterIteratorT, typename InnerIteratorT>
class MyIterator
{
public:
MyIterator() = default;
MyIterator(const OuterIteratorT& outer, const InnerIteratorT& inner) : m_outer(outer), m_inner(inner) { }
template <typename U, typename V>
MyIterator(const MyIterator<U, V>& rhs) : m_outer(rhs.m_outer), m_inner(rhs.m_inner) {}
decltype(auto) operator* () const { return *m_inner; }
decltype(auto) operator->() const { return &*m_inner; }
// Post- increment/decrement operators
MyIterator operator ++ (int) { const auto t = *this; ++*this; return t; }
MyIterator& operator ++ ()
{
// Вот тут эскизно.
// В реале, конечно же, сложнее.
// Но это никак не затрагивает вопросы, заданные тобой в старовом сообщении.if (++m_inner == m_outer->end())
{
++m_outer;
m_inner = m_outer->begin();
}
return *this;
}
template <typename U, typename V>
bool operator == (const MyIterator<U, V>& rhs) const { return m_outer == rhs.outer && m_inner == rhs.m_inner; }
template <typename U, typename V>
bool operator != (const MyIterator<U, V>& rhs) const { return (*this == rhs); }
private:
OuterIteratorT m_outer {};
InnerIteratorT m_inner {};
template <typename> friend class MyIterator;
};
// . . .template <typename T>
using iterator = MyIterator<typename std::vector<std::vector<T>>::iterator, typename std::vector<T>::iterator>;
template <typename T>
using const_iterator = MyIterator<typename std::vector<std::vector<T>>::const_iterator, typename std::vector<T>::const_iterator>;
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>В принципе, все то же самое, что и раньше, только шаблонный класс итератора теперь инкапсулирует два итератора и параметризуется двумя параметрами (компилятом не проверял):
Спасибо, все получилось.
В реальности потребовалось 4 параметра шаблона, но идея та же.
Кода меньше на 200 строчек получилось, чем в моем начальном варианте с двумя классами итераторов.
Здравствуйте, qaz77, Вы писали:
Q>Спасибо, все получилось. Q>В реальности потребовалось 4 параметра шаблона, но идея та же. Q>Кода меньше на 200 строчек получилось, чем в моем начальном варианте с двумя классами итераторов.
Класс!
Ну и еще одно соображение вдогонку: иногда так бывает, что параметры шаблона не являются полностью независимыми, и одни параметры можно вывести из других, используя относительно несложные метафункции. И тогда количество шаблонных параметров можно сократить. Но это уже по вкусу. Вполне может оказаться, что указать четыре параметра проще, чем городить огород, для того, чтобы уменьшить их до трех.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали: R>Ну и еще одно соображение вдогонку: иногда так бывает, что параметры шаблона не являются полностью независимыми, и одни параметры можно вывести из других, используя относительно несложные метафункции. И тогда количество шаблонных параметров можно сократить. Но это уже по вкусу. Вполне может оказаться, что указать четыре параметра проще, чем городить огород, для того, чтобы уменьшить их до трех.
Моя задача была сделать фрагментированный вектор, не требующий непрерывного блока памяти и, соответственно, менее требовательный к фрагментации адресного пространства. Реально некоторые клиенты софтины bad_alloc получали в реализации с std::vector, пришлось озадачиться...
Подошел бы deque, где можно указывать размер страницы. Но в реализации STL от MS размер страницы до смешного мал.
Соответственно, первый параметр шаблона — size_t, размер фрагмента.
Второй параметр — вектор фрагментов (конст/не конст), т.к. нужно получать vec->end() в функциях итерирования.
Третий и четвертый — итераторы векторов фрагментов и внутри фрагмента соответственно (тоже, или конст, или неконст).
Состояние end() для композитного контейнера я реализовал как it_outer == vec->end(), it_inner == inner_iterator().
Использую как fragmented_vector<T, 1024>. Вроде полет нормальный.