Приведение типов итераторов для контейнера с базовым типом
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 08:59
Оценка:
Есть контейнер, который хранит указатели на объекты:
template <typename Object>
class ObjectList : public IObject
{
public:
    typedef typename std::vector<Object *> list;
    typedef typename list::const_iterator const_iterator;
    typedef typename list::iterator iterator;

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

    iterator begin() { return items_.begin(); }
    iterator end() { return items_.end(); }
protected:
    list items_;
};


Есть некий базовый класс:
class BaseImportList : public ObjectList<BaseImport>
{
...
}


И есть потомок, который хранит в контейнере предка от BaseImport:
class MacImport : public BaseImport
{
...
}

class MacImportList : public BaseImportList
{
    typedef typename std::vector<MacImport *> list;
    list::const_iterator begin() const { return reinterpret_cast<const list &>(items_).begin(); }
    list::const_iterator end() const { return reinterpret_cast<const list &>(items_).end(); }
}


Вопрос, как вытащить "std::vector" из ObjectList для приведения типов итератора чтобы больше никогда не зависеть от типа контейнера в базовом классе?
Отредактировано 12.01.2024 9:00 drVanо . Предыдущая версия .
Re: Приведение типов итераторов для контейнера с базовым типом
От: andrey.desman  
Дата: 12.01.24 09:26
Оценка: +1
Здравствуйте, drVanо, Вы писали:

V>Вопрос, как вытащить "std::vector" из ObjectList для приведения типов итератора чтобы больше никогда не зависеть от типа контейнера в базовом классе?


Самое простое — оставить шаблон в BaseImportList. Иначе std::views::transform или transform_iterator из буста, или какой-нибудь свой cast_iterator.
Re[2]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 09:31
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Самое простое — оставить шаблон в BaseImportList. Иначе std::views::transform или transform_iterator из буста, или какой-нибудь свой cast_iterator.


BaseImportList не может быть шаблонным, т.к. через работает код, который знает только про BaseImport.

P.S. Использую С++11.
Отредактировано 12.01.2024 9:33 drVanо . Предыдущая версия .
Re[3]: Приведение типов итераторов для контейнера с базовым
От: Кодт Россия  
Дата: 12.01.24 10:41
Оценка:
Здравствуйте, drVanо, Вы писали:

V>BaseImportList не может быть шаблонным, т.к. через работает код, который знает только про BaseImport.


Это называется "дженерики для бедных"

Я бы подумал в такую сторону:
— нешаблонный базовый контейнер с vector<void*>
— шаблон типизированной обёртки доступа к нему
— шаблон типизированной обёртки хранения его
— операции приведения

struct BasePtrVec {
  typedef std::vector<void*> items_t;
  items_t items_;
};

template<class T> struct PtrVecTraits {
  struct iterator {
    BasePtrVec::items_t::iterator it;

    T*& operator*() const { return (T*&)*it; }
    T** operator->() const { return (T**)(it.operator->()); }
    .....
  };
  .....
};

// заготовка контейнера с функциями доступа
template<class T, class C> struct PtrVecCRTP : PtrVecTraits<T> {
  BasePtrVec& src() { return static_cast<C*>(this)->src_; }

  iterator begin() { return iterator(src().begin()); }
  size_t size() const { return src().size(); }
  T*& operator[] (size_t i) { return (T*&)src()[i]; }
  .....
};

// собственно, типизированный контейнер
template<class T> struct PtrVec : PtrVecCRTP<T, PtrVec<T>> {
  BasePtrVec src_;

  PtrVec() {}
  PtrVec(size_t n) : src_(n, nullptr) {}
  .....
};

// ссылка на контейнер - строго инвариантная
template<class T> struct PtrVecRef : PtrVecCRTP<T, PtrVecRef<T>> {
  BasePtrVec& src_;

  PtrVecRef(PtrVec& v) : src_(v.src_) {}  // ссылка на первоисточник - ок
};


// приведения типов, наверно, лучше делать явно руками

// downcast может снимать константность, upcast может накладывать
// ну или этот момент отдельно явно расписать

template<class Derived, class T>
std::enable_if< std::is_convertible<T*, Derived*>::value, PtrVecRef<Derived> >
ptr_vec_ref_downcast(PtrVecRef<T> r) { return PtrVecRef<Derived>(r.src_); }  // 

template<class Base, class T>
std::enable_if< std::is_convertible<Base*, T*>::value, PtrVecRef<Base> >
ptr_vec_ref_upcast(PtrVecRef<T> r) { return PtrVecRef<Base>(r.src_); }


Если упереться, то можно разрешить неявный кастинг вверх для константных контейнеров и ссылок.
То есть, константный контейнер наследников — это субкласс константного контейнера предков. (По принципу подстановки Лисков).
А вот кастинг вниз — тут уже будет веселье...
Перекуём баги на фичи!
Re: Приведение типов итераторов для контейнера с базовым тип
От: rg45 СССР  
Дата: 12.01.24 12:16
Оценка: +1
Здравствуйте, drVanо, Вы писали:

V>Есть контейнер, который хранит указатели на объекты:

V> . . .

V>Есть некий базовый класс:

V>
V>class BaseImportList : public ObjectList<BaseImport>
V>{
V>...
V>}
V>


V>И есть потомок, который хранит в контейнере предка от BaseImport:

V>
V>class MacImport : public BaseImport
V>{
V>...
V>}

V>class MacImportList : public BaseImportList
V>{
V>    typedef typename std::vector<MacImport *> list;
V>    list::const_iterator begin() const { return reinterpret_cast<const list &>(items_).begin(); }
V>    list::const_iterator end() const { return reinterpret_cast<const list &>(items_).end(); }
V>}
V>


V>Вопрос, как вытащить "std::vector" из ObjectList для приведения типов итератора чтобы больше никогда не зависеть от типа контейнера в базовом классе?


К сожалению, в C++ нет ковариантности контейнеров и не предвидится в обозримом будущем. Но в данном случае в ней (в ковариантности) нет и ососбой нужды. Насколько я могу судить по дизайну, класс MacImportList сам по себе имеет семантику контейнера, а значит, нет особого смысла выставлять наружу инкапсулированый контейнер, которые является деталью реализации класса. Достаточно просто предоставить адаптор итератора, который будет брать на себя преобразование к нужному типу. Схематично так:

template <typename T>
struct BaseImportListIteratorAdaptor : boost::iterator_adaptor<
   BaseImportListIteratorAdaptor<T>,   // This class
   BaseImportList::const_iterator,     // Base iterator
   T*,                                 // Value type
   boost::use_default,                 // Category of traversal
   T*                                  // Reference (not allowing real references to pointers)
>
{
   // Use concepts instead of the static_assert on C++20
   static_assert(std::is_base_of_v<BaseImport, std::remove_pointer_t<std::decay_t<T>>>);

   using BaseIterator = BaseImportList::const_iterator;
   using BaseAdaptor = boost::iterator_adaptor<
      BaseImportListIteratorAdaptor,
      BaseImportList::const_iterator,
      T*,
      boost::use_default,
      T*
   >;

   BaseImportListIteratorAdaptor() = default;
   BaseImportListIteratorAdaptor(const BaseIterator& i) : BaseAdaptor(i) {}

   T* dereference() const { return static_cast<T*>(*BaseAdaptor::base()); }
};

class MacImportList : public BaseImportList
{
public:
   using const_iterator = BaseImportListIteratorAdaptor<MacImport>;
   using iterator = const_iterator;

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


Ну а формирование инкапсулированного контейнера и его модификация должны проиводиться какими-то специализированными функциями. Прямого доступа на модификацию икапсулированного контейнера давать не следует.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 12.01.2024 12:48 rg45 . Предыдущая версия . Еще …
Отредактировано 12.01.2024 12:31 rg45 . Предыдущая версия .
Re[2]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 12:34
Оценка: -2 :))
Здравствуйте, rg45, Вы писали:

R>Насколько я могу судить по дизайну, класс MacImportList сам по себе имеет семантику контейнера


Контейнер (items_) физически находится в ObjectList<T>. Иерархия классов выглядит так:

MacImport — BaseImport
MacImportList — BaseImportList — ObjectList<BaseImport>

Поэтому мне проще кастонуть сам контейнер к нужному типу и уже от него взять тип итератора. Пока остановился на таком варианте:
class MacImportList : public BaseImportList
{
    typedef typename ObjectList<MacImport>::container container;
    typedef typename container::const_iterator const_iterator;
    const_iterator begin() const { return reinterpret_cast<const container &>(items_).begin(); }
    const_iterator end() const { return reinterpret_cast<const container &>(items_).end(); }
}

template <typename Object>
class ObjectList : public IObject
{
    typedef typename std::vector<Object *> container;
    typedef typename std::vector<Object *>::const_iterator const_iterator;

    const_iterator begin() const { return items_.begin(); }
    const_iterator end() const { return items_.end(); }
protected:
    container items_;
};
Отредактировано 12.01.2024 12:35 drVanо . Предыдущая версия .
Re[2]: Приведение типов итераторов для контейнера с базовым тип
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 13:19
Оценка:
Здравствуйте, rg45, Вы писали:

R>class MacImportList : public BaseImportList

R>{
R>public:
R> using const_iterator = BaseImportListIteratorAdaptor<MacImport>;
R> using iterator = const_iterator;

R> const_iterator begin() const { return items_.begin(); }

R> const_iterator end() const { return items_.end(); }
R>};
R>[/cpp]

Я правильно понимаю, что вы забыли приведение типов вокруг "items_.begin()" и "items_.end()"?
Re[3]: Приведение типов итераторов для контейнера с базовым тип
От: rg45 СССР  
Дата: 12.01.24 13:26
Оценка:
Здравствуйте, drVanо, Вы писали:


R>>class MacImportList : public BaseImportList

R>>{
R>>public:
R>> using const_iterator = BaseImportListIteratorAdaptor<MacImport>;
R>> using iterator = const_iterator;

R>> const_iterator begin() const { return items_.begin(); }

R>> const_iterator end() const { return items_.end(); }
R>>};
R>>[/cpp]

V>Я правильно понимаю, что вы забыли приведение типов вокруг "items_.begin()" и "items_.end()"?


Нет, неправильно. Предоставленный выше адатпор предоставляет соответствующий неявный конструктор, по итератору базового типа, поэтому явного преобразования не требуется:

BaseImportListIteratorAdaptor(const BaseIterator& i) : BaseAdaptor(i) {}


Вообще, если одолевают какие-то сомнения, то вот компилирующийся фрагмент кода: http://coliru.stacked-crooked.com/a/f9b794b137e4159c
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: Приведение типов итераторов для контейнера с базовым тип
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 13:37
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>BaseImportListIteratorAdaptor(const BaseIterator& i) : BaseAdaptor(i) {}
R>


R>Вообще, если одолевают какие-то сомнения, то вот компилирующийся фрагмент кода: http://coliru.stacked-crooked.com/a/f9b794b137e4159c


Последний вопрос — как это можно реализовать без boost?
Re[5]: Приведение типов итераторов для контейнера с базовым
От: rg45 СССР  
Дата: 12.01.24 14:04
Оценка: 9 (1)
Здравствуйте, drVanо, Вы писали:

V>Последний вопрос — как это можно реализовать без boost?


Ну, просто реализуешь свой собственный класс итератора и обеспечиваешь его необходимым набором операций — сравнение, разыменование, инкремент и пр.

Вот здесь рабочий пример: http://coliru.stacked-crooked.com/a/cd2a320d4ecdd270

template <typename T>
class BaseImportListIteratorAdaptor
{
public:
   // Use concepts instead of the static_assert on C++20
   static_assert(std::is_base_of_v<BaseImport, T>);
   using BaseIterator = BaseImportList::const_iterator;

   BaseImportListIteratorAdaptor() = default;
   BaseImportListIteratorAdaptor(const BaseIterator& i) : m_baseIterator(i) {}

   T* operator*() const { return static_cast<T*>(*m_baseIterator); }

   bool operator == (const BaseImportListIteratorAdaptor& rhs) const  { return m_baseIterator == rhs.m_baseIterator; }
   bool operator != (const BaseImportListIteratorAdaptor& rhs) const { return !(*this == rhs); }

   BaseImportListIteratorAdaptor& operator++() { ++m_baseIterator; return *this; }
   BaseImportListIteratorAdaptor operator++(int) { return m_baseIterator++; }

private:
   BaseIterator m_baseIterator;
};


Просто чем хорош boost::iterator_adaptor — он определяет полный джентльменский набор всех атрибутов итератора — методы, типы, категорию и пр.

Да, еще один момент: адапторы итераторов, на самом деле, классная штука, с помощью которой можно адаптировать разного рода коллекции под конкретные нужды. Например, ты можешь подпилить этот адаптор так, чтоб он возвращал не указатели, а сразу ссылки на объекты. Снаружи MacImportList будет выглядеть как обычный контейнер объектов класса MacImport.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 12.01.2024 14:51 rg45 . Предыдущая версия . Еще …
Отредактировано 12.01.2024 14:17 rg45 . Предыдущая версия .
Отредактировано 12.01.2024 14:17 rg45 . Предыдущая версия .
Отредактировано 12.01.2024 14:10 rg45 . Предыдущая версия .
Отредактировано 12.01.2024 14:09 rg45 . Предыдущая версия .
Отредактировано 12.01.2024 14:09 rg45 . Предыдущая версия .
Отредактировано 12.01.2024 14:05 rg45 . Предыдущая версия .
Re[6]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 12.01.24 14:23
Оценка: 1 (1) :)
Здравствуйте, rg45, Вы писали:

R>Да, еще один момент: адапторы итераторов, на самом деле, классная штука, с помощью которой можно адаптировать разного рода коллекции под конкретные нужды. Например, ты можешь подпилить этот адаптор так, чтоб он возвращал не указатели, а сразу ссылки на объекты. Снаружи это будет выглядеть как вектор объектов класса MacImport.


Класс! Засунул адаптор внутрь ObjectList, теперь "снаружи" нужен только один тип для адаптора для всех потомков.
Отредактировано 12.01.2024 14:25 drVanо . Предыдущая версия . Еще …
Отредактировано 12.01.2024 14:23 drVanо . Предыдущая версия .
Re[6]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 17.01.24 05:33
Оценка:
Здравствуйте, rg45, Вы писали:

R>Да, еще один момент: адапторы итераторов, на самом деле, классная штука, с помощью которой можно адаптировать разного рода коллекции под конкретные нужды. Например, ты можешь подпилить этот адаптор так, чтоб он возвращал не указатели, а сразу ссылки на объекты.


Можно какой-нибудь простой пример такого адаптора к std::vector<std::unique_ptr<T>>?
Re[7]: Приведение типов итераторов для контейнера с базовым
От: rg45 СССР  
Дата: 17.01.24 09:04
Оценка:
Здравствуйте, drVanо, Вы писали:

R>>Да, еще один момент: адапторы итераторов, на самом деле, классная штука, с помощью которой можно адаптировать разного рода коллекции под конкретные нужды. Например, ты можешь подпилить этот адаптор так, чтоб он возвращал не указатели, а сразу ссылки на объекты.


V>Можно какой-нибудь простой пример такого адаптора к std::vector<std::unique_ptr<T>>?


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

В примере ниже определены два простых адаптора: IndirectIterator и StaticCastIterator. Эти два адаптора комбинируются (StaticCastIterator поверх IndirectIterator) и применяются к инкапсулированному контейнеру std::vector<std::unique_ptr<T>>. В принципе, вместо unique_ptr можно подставить любой другой вид указателя и будет работать точно так же. В примере присутствует избыточный копи-пастинг — это ради того, чтоб не перегружать пример дополнительными сущностями и не подключать boost. По-хорошему, такого быть не должно, конечно же (для этого и придуманы шаблоны boost::iterator_facade и boost::iterator_adaptor).

http://coliru.stacked-crooked.com/a/8764c68363edc011

#include <vector>
#include <iostream>
#include <memory>
#include <type_traits>
#include <utility>

template <typename BaseIterator>
class IndirectIterator
{
public:

   using TargetType = decltype(**std::declval<BaseIterator>());

   IndirectIterator() = default;
   IndirectIterator(const BaseIterator& i) : m_baseIterator(i) {}

   TargetType operator*() const { return **m_baseIterator; }

   bool operator == (const IndirectIterator& rhs) const { return m_baseIterator == rhs.m_baseIterator; }
   bool operator != (const IndirectIterator& rhs) const { return !(*this == rhs); }

   IndirectIterator& operator++() { ++m_baseIterator; return *this; }
   IndirectIterator operator++(int) { return m_baseIterator++; }

private:
   BaseIterator m_baseIterator;
};


template <typename BaseIterator, typename TargetType>
class StaticCastIterator
{
public:

   StaticCastIterator() = default;
   StaticCastIterator(const BaseIterator& i) : m_baseIterator(i) {}

   TargetType operator*() const { return static_cast<TargetType>(*m_baseIterator); }

   bool operator == (const StaticCastIterator& rhs) const { return m_baseIterator == rhs.m_baseIterator; }
   bool operator != (const StaticCastIterator& rhs) const { return !(*this == rhs); }

   StaticCastIterator& operator++() { ++m_baseIterator; return *this; }
   StaticCastIterator operator++(int) { return m_baseIterator++; }

private:
   BaseIterator m_baseIterator;
};


template <typename T>
class BaseList
{
   using Items_ = std::vector<std::unique_ptr<T>>;
public:

   using iterator = IndirectIterator<typename Items_::iterator>;
   using const_iterator = IndirectIterator<typename Items_::const_iterator>;

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

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

protected:
   Items_ items_;
};

class BaseImport {};

template <typename T>
class ImportList : public BaseList<BaseImport>
{
public:

   // Use concepts instead of the static_assert on C++20
   static_assert(std::is_base_of_v<BaseImport, T>);

   using BaseList = BaseList<BaseImport>;
   using iterator = StaticCastIterator<typename BaseList::iterator, T&>;
   using const_iterator = StaticCastIterator<typename BaseList::const_iterator, const T&>;

   iterator begin() { return BaseList::begin(); }
   iterator end() { return BaseList::end(); }

   const_iterator begin() const { return BaseList::begin(); }
   const_iterator end() const { return BaseList::end(); }

   template <typename... CreationParams>
   void CreateItem(CreationParams&&... params) {
      BaseList::items_.emplace_back(std::make_unique<T>(std::forward<CreationParams>(params)...));
   }
};

struct MacImport : BaseImport
{
   int id{};
   std::string name;

   MacImport(int id, const std::string& name) : id(id), name(name){}
};

int main()
{
   using MacImportList = ImportList<MacImport>;

   MacImportList l;

   l.CreateItem(101, "Mac Item 101");
   l.CreateItem(102, "Mac Item 102");
   l.CreateItem(103, "Mac Item 103");

   for (const MacImport& item : l)
   {
      std::cout << "[" << item.id << "]: " << item.name << std::endl;
   }
}


Output:
[101]: Mac Item 101
[102]: Mac Item 102
[103]: Mac Item 103
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 17.01.2024 10:56 rg45 . Предыдущая версия . Еще …
Отредактировано 17.01.2024 9:10 rg45 . Предыдущая версия .
Отредактировано 17.01.2024 9:08 rg45 . Предыдущая версия .
Отредактировано 17.01.2024 9:07 rg45 . Предыдущая версия .
Отредактировано 17.01.2024 9:05 rg45 . Предыдущая версия .
Отредактировано 17.01.2024 9:05 rg45 . Предыдущая версия .
Re[7]: Приведение типов итераторов для контейнера с базовым
От: rg45 СССР  
Дата: 17.01.24 10:00
Оценка: 9 (1)
Здравствуйте, drVanо, Вы писали:

V>Можно какой-нибудь простой пример такого адаптора к std::vector<std::unique_ptr<T>>?


В дополнение к вышесказанному. Адаптеры можно писать не только для итераторов, но и для целых контейнеров. Можно, например, написать обобщенный шаблонный адаптор для std::vector и поддержать в нем ковариантность (типа CovariantVector). Тогда каждый такой контейнер будет автоматом являться и контейнером элементов базовых классов. Можно пойди еще дальше и сделать еще более обобщенный шаблон, который можно будет использовать не только с вектором, но и с другими контейнерами, типа того как это сделано в std::queue и std::stack. Правда, здесь возникают некоторые вопросы с принципом Лисков, возможно, именно поэтому такие адапторы отсутствуют в стандартной библиотеке, но, как частное решение для собственных нужд, это может иметь право на существование.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 17.01.2024 10:06 rg45 . Предыдущая версия .
Re[8]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 17.01.24 10:58
Оценка:
Здравствуйте, rg45, Вы писали:

R>В дополнение к вышесказанному. Адаптеры можно писать не только для итераторов, но и для целых контейнеров. Можно, например, написать обобщенный шаблонный адаптор для std::vector и поддержать в нем ковариантность (типа CovariantVector). Тогда каждый такой контейнер будет автоматом являться и контейнером элементов базовых классов. Можно пойди еще дальше и сделать еще более обобщенный шаблон, который можно будет использовать не только с вектором, но и с другими контейнерами, типа того как это сделано в std::queue и std::stack.


Мы можем пообщаться через telegram?

P.S. Написал в личку — не знаю дойдет или нет.
Re[9]: Приведение типов итераторов для контейнера с базовым
От: rg45 СССР  
Дата: 17.01.24 11:18
Оценка:
Здравствуйте, drVanо, Вы писали:

V>Мы можем пообщаться через telegram?


Лучше здесь. Ты напиши вопросы, я отвечу. А кто-нибудь, возможно, дополнит/поправит.

V>P.S. Написал в личку — не знаю дойдет или нет.


Не, не пришло.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[10]: Приведение типов итераторов для контейнера с базовым
От: drVanо Россия https://vmpsoft.com
Дата: 17.01.24 11:25
Оценка:
Здравствуйте, rg45, Вы писали:

R>Лучше здесь. Ты напиши вопросы, я отвечу. А кто-нибудь, возможно, дополнит/поправит.


Я хотел пообщаться на темы, которые выходят за рамки этого форума, хотя напрямую связаны с C++.

Мой телеграм: https://t.me/vmpsoft
Re[11]: Приведение типов итераторов для контейнера с базовым
От: rg45 СССР  
Дата: 17.01.24 11:47
Оценка:
Здравствуйте, drVanо, Вы писали:

V>Я хотел пообщаться на темы, которые выходят за рамки этого форума, хотя напрямую связаны с C++.

V>Мой телеграм: https://t.me/vmpsoft

Пока нет возможности, к сожалению.
--
Не можешь достичь желаемого — пожелай достигнутого.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.