Использование диапазона for для цикла со сторонним контейнером

В настоящее время я использую стороннюю библиотеку, которая содержит класс, который обеспечивает только индексированный поиск, т.е. operator[],

Я хотел бы сделать цикл на основе диапазона для содержимого этого класса. Однако, никогда не писав итератор или адаптер итератора, я совершенно растерялся. Кажется, что написание итераторов не простая задача.

Мое желаемое использование:

for(auto element : container)
{
...
}

Вместо того, чтобы писать:

for(int i = 0; i < container.size(); ++i)
{
auto element = container[i];
...
}

Как этого достичь? Предоставляет ли Boost эту функциональность?

1

Решение

Написание итераторов на самом деле довольно простая задача, но она становится чрезвычайно утомительной. Поскольку ваш контейнер поддерживает индексирование по целому числу, я предполагаю, что его итераторы попадут в категорию итераторов с произвольным доступом (если они существуют). Это требует много шаблонного!

Однако для поддержки цикла for, основанного на диапазоне, все, что вам нужно, это прямой итератор. Мы напишем простую оболочку для контейнера, которая реализует требования прямого итератора, а затем напишем две функции Iterator begin(Container&) а также Iterator end(Container&) которые позволяют использовать контейнер в цикле for на основе диапазона.

это Iterator будет содержать ссылку на контейнер, размер контейнера и текущий индекс в этом контейнере:

template<template<typename> class C, typename T>
class indexer : public std::iterator<std::forward_iterator, T>
{
public:
indexer(C<T>& c, std::size_t i, std::size_t end)
: c_(std::addressof(c)), i_(i), end_(end) {}

T& operator*() const {
return c_[i_];
}

indexer<C, T>& operator++() {
++i_;
return *this;
}
indexer<C, T> operator++(int) {
auto&& tmp = *this;
operator++();
return tmp;
}

bool operator==(indexer<C, T> const& other) const {
return i_ == other.i_;
}
bool operator!=(indexer<C, T> const& other) const {
return !(*this == other);
}

private:
C<T>* c_;
std::size_t i_, end_;
};

Наследование от std::iterator удобно объявляет соответствующие typedefs для использования с std::iterator_traits,

Затем вы бы определить begin а также end следующее:

template<typename T>
indexer<Container, T> begin(Container<T>& c) {
return indexer<Container, T>(c, 0, c.size());
}

template<typename T>
indexer<Container, T> end(Container<T>& c) {
auto size = c.size();
return indexer<Container, T>(c, size, size);
}

Выключить Container для любого типа container в вашем примере, и с этим, ваше желаемое использование работает!

Требования и поведение всех различных типов итераторов можно найти в таблицах раздела 24.2.2 стандарта, которые отражены в cppreference.com здесь.

Реализация описанного выше итератора с произвольным доступом, а также демонстрация использования с простым vector_view класс можно найти в прямом эфире Coliru или же ideone.com.

3

Другие решения

Вы можете сделать следующее:

1) определить свой собственный итератор It внутри которого содержится ссылка ref в ваш контейнер container и индекс i, Когда итератор разыменовывается, он возвращает ref[i] по ссылке. Вы можете написать его самостоятельно или использовать boost для справки, он имеет библиотеку итераторов, чтобы легко определять свои собственные итераторы. Конструктор должен принять container& и size_t, Вы можете сделать также const_iterator если эта концепция применима.

2) Когда operator++ вызывается на одном итераторе, он просто делает ++i на внутреннем члене. operator== а также operator!= следует просто сравнить i, Вы можете assertдля безопасности, что оба итератора являются связными, что означает их refs указывают на один и тот же объект.

3) добавить begin а также end к вашему классу контейнера или, если это невозможно, определить глобальный begin а также end которые принимают ваши container& c, begin должен просто вернуться It(c, 0), end должен вернуться It(c, c.size()),

Может быть проблема с копированием итераторов, так как они содержат ссылку и некоторые другие мелкие детали, но я надеюсь, что общая идея ясна и правильна.

1