про итераторы
От: qaz77  
Дата: 27.03.20 09:00
Оценка:
Коллеги, проконсультируйте плз.

Делаю 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.
Пришлось добавлять:
class iterator
{
...
  difference_type operator-(const const_iterator& other) const;

  bool operator==(const const_iterator& other) const;
  // !=, <, > и т.д
};


Как-то громоздко получается и нет полной уверенности, что еще чего-то не учел.

Посмотрел, как решена задача в MS реализации STL для вектора.
Там iterator наследуют от const_iterator.
Это удобно, но в моем случае не подходит, т.к. члены данных в моих iterator и const_iterator разного типа (итераторы нижележащих контейнеров).
Re: про итераторы
От: rg45 СССР  
Дата: 27.03.20 10:55
Оценка: 5 (2)
Здравствуйте, qaz77, Вы писали:

Q>Делаю STL-совместимый самодельный контейнер, который обертка над стандартными контейнерами.

Q>Соответственно, потребовались iterator и const_iterator.
Q>Вопрос про то, как лучше/удобнее реализовать эту парочку, с минимумом копипасты и односторонней заменяемости от iterator к const_iterator.
Q>Что-то я не нашел гайдлайнов для такой задачи...

Типовое решение для подобных зачач предлагает Boost Iterator Library.

Здесь есть два шаблонных класса:
iterator_facade
iterator_adaptor

Первый класс дает больше возможностей, второй — более прост в обращении. Идея же в обоих случаях одинаковая — ты определяешь один общий шаблонный класс, а константная и неконстантная версии итераторов являются обычными алиасами этого шаблона, параметризованного чуть по-разному. Interoperability достигается очень просто: один шаблонный конструктор и шаблонные операторы сравнения. Все! Не нужно наследования. Не нужно операторов присваивания. Ни в коем случае не делай операторов неявного преобразования, если не хочешь проблем, вызванных неоднозначностью.

Ознакомься внимательно с Tutorial Example из iterator facade, чтобы в деталях понять идею. Особое внимание обрати на пункт "Interoperability". Ну а что именно тебе больше подходит — facade или adaptor — уже разберешься самостоятельно.

Удачи!
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: про итераторы
От: rg45 СССР  
Дата: 27.03.20 13:26
Оценка: 3 (1)
Здравствуйте, qaz77, Вы писали:

Q>Делаю STL-совместимый самодельный контейнер, который обертка над стандартными контейнерами.

Q>Соответственно, потребовались iterator и const_iterator.
Q>Вопрос про то, как лучше/удобнее реализовать эту парочку, с минимумом копипасты и односторонней заменяемости от iterator к const_iterator.
Q>Что-то я не нашел гайдлайнов для такой задачи...

Я вот здесь набросал простейший proof-of-concept контейнера-враппера для std::vector и враппера для итераторов с использованием boost::iterator_adaptor. Как видишь, все, что потребовалось — это один единственный дополнительный конструктор. Даже операторы сравнения и вычитания не пришлось определять самому:

http://coliru.stacked-crooked.com/a/dcd22f47ba5d7da7

#include <iostream>
#include <vector>
#include <boost/iterator/iterator_adaptor.hpp>

template <typename BaseIteratorT>
class MyIterator
 : public boost::iterator_adaptor<MyIterator<BaseIteratorT>, BaseIteratorT>
{
    using iterator_adaptor = boost::iterator_adaptor<MyIterator<BaseIteratorT>, BaseIteratorT>;

 public:

    using iterator_adaptor::iterator_adaptor;

    MyIterator() = default;

    template <typename U>
    MyIterator(const MyIterator<U>& rhs) : iterator_adaptor(rhs.base()) {}

};

template <typename T>
class MyVector
{
public:

    using iterator = MyIterator<typename std::vector<T>::iterator>;
    using const_iterator = MyIterator<typename std::vector<T>::const_iterator>;

    MyVector() = default;
    MyVector(std::initializer_list<T> l) : m_v{l} {}

    iterator begin() { return iterator(m_v.begin()); }
    iterator end() { return iterator(m_v.end()); }

    const_iterator begin() const { return const_iterator(m_v.begin()); }
    const_iterator end() const { return const_iterator(m_v.end()); }

private:
    std::vector<T> m_v;
};

int main() {

    MyVector<int> my_v {0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    for (const auto& e : my_v)
    {
        std::cout << " " << e;
    }
    std::cout << std::endl;


    MyVector<int>::iterator begin = my_v.begin();
    MyVector<int>::const_iterator end = my_v.end();

    std::cout << std::boolalpha;
    std::cout << (begin == end) << std::endl; // false
    std::cout << (begin != end) << std::endl; // true
    std::cout << (begin <= end) << std::endl; // true
    std::cout << (end - begin) << std::endl; // 10

}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 27.03.2020 13:38 rg45 . Предыдущая версия .
Re[2]: про итераторы
От: qaz77  
Дата: 27.03.20 14:12
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я вот здесь набросал простейший proof-of-concept контейнера-враппера для std::vector и враппера для итераторов с использованием boost::iterator_adaptor. Как видишь, все, что потребовалось — это один единственный дополнительный конструктор. Даже операторы сравнения и вычитания не пришлось определять самому:


Спасибо, буду разбираться.
К сожалению, проект у меня без буста...
Re[3]: про итераторы
От: rg45 СССР  
Дата: 27.03.20 14:16
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Спасибо, буду разбираться.

Q>К сожалению, проект у меня без буста...

Но можно позаимствовать идею. Собственная версия, скорее всего, будет проще бустовой благодаря своей более узкой применимости.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: про итераторы
От: qaz77  
Дата: 27.03.20 14:25
Оценка:
Здравствуйте, rg45, Вы писали:

R>Но можно позаимствовать идею. Собственная версия, скорее всего, будет проще бустовой благодаря своей более узкой применимости.


Да, в это и подразумевал под "разбираться".

Просто мой вариант показался слишком громоздким, хотя работает и все тесты проходит.
Re[5]: про итераторы
От: rg45 СССР  
Дата: 27.03.20 14:57
Оценка: 2 (1)
Здравствуйте, qaz77, Вы писали:

Q>Да, в это и подразумевал под "разбираться".

Q>Просто мой вариант показался слишком громоздким, хотя работает и все тесты проходит.

А чтоб легче было разбираться, вот тебе то же самое, только без буста, в ОЧЕНЬ-ОЧЕНЬ упрощенном виде. По крайней мере, на те вопросы, которые ты задавал в топике, этот пример отвечает:

http://coliru.stacked-crooked.com/a/e456adffd638290f

template <typename BaseIteratorT>
class MyIterator
{
 public:

    MyIterator() = default;

    explicit MyIterator(const BaseIteratorT& it) : m_it(it) { }

    template <typename U>
    MyIterator(const MyIterator<U>& rhs) : m_it(rhs.m_it) {}

    decltype(auto) operator* () const { return *m_it; }
    decltype(auto) operator->() const { return &*m_it; }

    MyIterator& operator ++ () { ++m_it; return *this; }
    MyIterator& operator -- () { --m_it; return *this; }

    // Post- increment/decrement operators
    MyIterator operator ++ (int) { const auto t = *this; ++m_it; return t; }
    MyIterator operator -- (int) { const auto t = *this; --m_it; return t; }

    template <typename U>
    std::ptrdiff_t operator - (const MyIterator<U>& rhs) const { return m_it - rhs.m_it; }

    template <typename U>
    bool operator == (const MyIterator<U>& rhs) const { return m_it == rhs.m_it; }

    template <typename U>
    bool operator != (const MyIterator<U>& rhs) const { return m_it != rhs.m_it; }

    template <typename U>
    bool operator < (const MyIterator<U>& rhs) const { return m_it < rhs.m_it; }

    template <typename U>
    bool operator > (const MyIterator<U>& rhs) const { return m_it > rhs.m_it; }

    template <typename U>
    bool operator <= (const MyIterator<U>& rhs) const { return m_it <= rhs.m_it; }

    template <typename U>
    bool operator >= (const MyIterator<U>& rhs) const { return m_it >= rhs.m_it; }

private:
    BaseIteratorT m_it{};

    template <typename> friend class MyIterator;
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[6]: про итераторы
От: qaz77  
Дата: 27.03.20 15:24
Оценка:
Здравствуйте, rg45, Вы писали:
R>А чтоб легче было разбираться, вот тебе то же самое, только без буста, в ОЧЕНЬ-ОЧЕНЬ упрощенном виде.

У меня не такой случай, когда надо адаптировать один готовый итератор.
Реализация инкремента, разницы и т.д. своя.

Упрощенно есть std::vector<std::vector<T>>, надо итерироваться с посещением вложенных векторов.
У меня получается что реализация iterator требует:
class vv_iterator
{
...
private:
  std::vector<std::vector<T>>* m_vec_outer;
  std::vector<std::vector<T>>::iterator m_it_outer;
  std::vector<T>::iterator m_it_inner;
};

А реализация const_iterator требует:
class vv_const_iterator
{
...
private:
  const std::vector<std::vector<T>>* m_vec_outer;
  std::vector<std::vector<T>>::const_iterator m_it_outer;
  std::vector<T>::const_iterator m_it_inner;
};


Типы членов данных можно засунуть в параметры шаблона обобщенного итератора.
Если теперь сравнения, разницу и конструктор сделать шаблонными членами, то асимметрия конст/не-конст будет достигаться возможностью копирования соответствующих членов данных.
Кажется должно сработать. Спасибо за подсказки.
Re[7]: про итераторы
От: rg45 СССР  
Дата: 27.03.20 15:59
Оценка: 3 (1)
Здравствуйте, 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>;
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 27.03.2020 19:37 rg45 . Предыдущая версия . Еще …
Отредактировано 27.03.2020 19:16 rg45 . Предыдущая версия .
Отредактировано 27.03.2020 16:40 rg45 . Предыдущая версия .
Re[8]: про итераторы
От: qaz77  
Дата: 30.03.20 08:27
Оценка: 1 (1)
Здравствуйте, rg45, Вы писали:

R>В принципе, все то же самое, что и раньше, только шаблонный класс итератора теперь инкапсулирует два итератора и параметризуется двумя параметрами (компилятом не проверял):


Спасибо, все получилось.
В реальности потребовалось 4 параметра шаблона, но идея та же.
Кода меньше на 200 строчек получилось, чем в моем начальном варианте с двумя классами итераторов.
Re[9]: про итераторы
От: rg45 СССР  
Дата: 30.03.20 08:54
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Спасибо, все получилось.

Q>В реальности потребовалось 4 параметра шаблона, но идея та же.
Q>Кода меньше на 200 строчек получилось, чем в моем начальном варианте с двумя классами итераторов.

Класс!

Ну и еще одно соображение вдогонку: иногда так бывает, что параметры шаблона не являются полностью независимыми, и одни параметры можно вывести из других, используя относительно несложные метафункции. И тогда количество шаблонных параметров можно сократить. Но это уже по вкусу. Вполне может оказаться, что указать четыре параметра проще, чем городить огород, для того, чтобы уменьшить их до трех.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[10]: про итераторы
От: qaz77  
Дата: 31.03.20 13:27
Оценка:
Здравствуйте, 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>. Вроде полет нормальный.
Re[11]: про итераторы
От: rg45 СССР  
Дата: 31.03.20 13:35
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Третий и четвертый — итераторы векторов фрагментов и внутри фрагмента соответственно (тоже, или конст, или неконст).


Ну вот четвертый можно было бы вывести из третьего, ну или наоборот. Но, имхо, оно того не стоит, чтоб пыль глотать
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 31.03.2020 13:59 rg45 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.