Итератор для vector<vector<T> >
От: LightGreen  
Дата: 12.01.14 12:21
Оценка: 1 (1)
Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.
Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():
template <class T>
class MyContainer
{
   typedef std::vector<T> Inner;
   typedef std::vector<Inner> Outer;
   Outer contents_;
public:
   class iterator
   {
      friend class MyContainer;
      Outer::iterator it1_;
      Inner::iterator it2_, it2end_;
      bool isEnd_; // <<<<< костыль
      iterator(Outer::iterator it1, bool) : it1_(it1), isEnd(false) {
         it2_ = it1_->begin();
         it2end_ = it1_->end();
      }
      iterator(Outer::iterator it1) : it1_(it1), isEnd(true) {}
   public:
      T& operator*() { return *it2_; }
      iterator& operator++() {
         if(++it2_==it2end_) {
            ++it1_;
            it2_ = it1_->begin();
            it2end_ = it1_->end();
         }
      }
      bool operator==(iterator it) const {
         return it1_==it.it1_ &&
            (isEnd_==it.isEnd_ || it2_==it.it2_);
      }
   };
   
   iterator begin() { return iterator(contents_.begin(), true); }
   iterator end() { return iterator(contents_.end()); }
};


В этом коде используется костыль в виде дополнительной переменной isEnd_, которая устанавливается в true для конца первого контейнера (когда значения it2_ и it2end_ не определены). Это увеличивает размер данных под итератор и количество операций сравнения в шаге цикла for(;it!=itEnd; ). Можно ли данную задачу решить изящнее, без дополнительной переменной и дополнительного сравнения?
Re: Итератор для vector<vector<T> >
От: LightGreen  
Дата: 12.01.14 12:31
Оценка:
Составляя пример кода выше, я пропустил ещё одну переменную класса it1end_, без которой нельзя в operator++() определить наступление условия isEnd_. Хотя тогда можно попробовать обойтись без isEnd_ и применять такой оператор сравнения:
[ccode]
bool operator=(iterator it) const {
return it1_==it.it1_ && (it1_==it1end_ || it2_==it.it2_);
}
Тем не менее, при этом в итераторе по-прежнему нужно хранить четыре переменные и большинство сравнений в цикле for() для объединённого контейнера будет состоять из трёх сравнений. Что намного хуже с вычислительной точки зрения, чем реализация в виде вложенных циклов, где в таком случае нужно всего одно сравнение.
Re: Итератор для vector<vector<T> >
От: Abyx Россия  
Дата: 12.01.14 13:43
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.

LG>Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():

просто не продвигайте итератор внутреннего вектора (it2_).
тогда он будет либо default-constructed для пустого контейнера, либо contents_.back().end()
In Zen We Trust
Re[2]: Итератор для vector<vector<T> >
От: Abyx Россия  
Дата: 12.01.14 13:47
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, LightGreen, Вы писали:


LG>>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.

LG>>Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():

A>просто не продвигайте итератор внутреннего вектора (it2_).

A>тогда он будет либо default-constructed для пустого контейнера, либо contents_.back().end()

хотя не, вру. default-constructed итераторы нельзя сравнивать, по кр.мере в MSVC.
тогда нужна статическая или глобальная переменная-вектор, чтобы у нее брать end() итератор, вместо default-constructed.
In Zen We Trust
Re: Итератор для vector<vector<T> >
От: night beast СССР  
Дата: 12.01.14 15:49
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.

LG>Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():

LG>template <class T>
LG>   class iterator
LG>   {
LG>      Outer::iterator it1_;
LG>      Inner::iterator it2_, it2end_;

делай по-другому, храни два внешних итератора (текущий и конечный) и один внутренний -- текущий.

LG>      iterator& operator++() {
LG>         if(++it2_==it2end_) {
LG>            ++it1_;
LG>            it2_ = it1_->begin();
LG>            it2end_ = it1_->end();
LG>         }

здесь проблема. мало того что при it1 == end получишь неприятности, но и при пустом внутреннем контейнере разименуешь it2end_.

LG>      }

LG>      bool operator==(iterator it) const {
LG>         return it1_==it.it1_ &&
LG>            (isEnd_==it.isEnd_ || it2_==it.it2_);

здесь можно обойтись сравнением it2_==it.it2_

LG>      }
LG>   };
Re[2]: Итератор для vector<vector<T> >
От: night beast СССР  
Дата: 12.01.14 17:06
Оценка:
Здравствуйте, night beast, Вы писали:

LG>>      }

LG>>      bool operator==(iterator it) const {
LG>>         return it1_==it.it1_ &&
LG>>            (isEnd_==it.isEnd_ || it2_==it.it2_);

NB>здесь можно обойтись сравнением it2_==it.it2_

чуть подумал. похоже, конец вектора все-же придется проверять  :(  it1_ == it1end_ ? it1_ == it.it1_ : it2_ == it.it2_.
ну или завернуть it2_ в optional.

LG>>      }
LG>>   };
Re: Итератор для vector<vector<T> >
От: Кодт Россия  
Дата: 12.01.14 17:53
Оценка: 14 (3)
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.


<>

Попробуем сделать наивную и ошибкоопасную реализацию такого итератора.
(Чтоб не путаться в терминах коллекций и элементов, представим себе, что имеем дело с текстом, строками и буквами).

template<class I, class J> struct jagged_iterator
{
  I i; J j;

  jagged_iterator(I i0, J j0) // при условии, что j0 относится к строке *i
  {
    i = i0; j = j0;
    skip_empty_lines(); // возможно, что мы встали в конец первой строки, - нужно прыгнуть вперёд.
  }
  jagged_iterator operator++()
  {
    // если это инкремент, то мы заведомо не в конце текста и не в конце строки (стоим на какой-то букве)
    ++j; // прыгнем на одну букву
    skip_empty_lines(); // и перепрыгнем, при необходимости, через конец строки и следующие пустые строки
  }
  void skip_empty_lines()
  {
    if(j==end(*i)) // если встали на конец строки,
    {
      do { ++i; } while(begin(*i)==end(*i)); // начнём прыгать по строкам, пока не найдём непустую
      j = begin(*i); // и встанем на её начало
    }
  }
  bool operator==(jagged_iterator const& other) const
  {
    return i==other.i && j==other.j;
  }

  decltype(*j) operator* () const { return *j; }
};

Очевидно, что он будет работать на непустых текстах, до последней буквы.
Инкремент с последней буквы приведёт нас к аварии.

Как на старте, так и на каждом инкременте нам нужно знать, где останавливаться.
Поэтому добавим итератор концевой строки, чтобы заведомо не вылететь за конец текста.
template<class I, class J> struct jagged_iterator
{
  I i;
  I ie; J j;

  jagged_iterator(I i0, I iend, J j0) // при условии, что если i!=iend, j0 относится к строке *i
  {
    i = i0; ie = iend; j = j0;
    skip_empty_lines();
  }
  jagged_iterator operator++()
  {
    ++j;
    skip_empty_lines();
  }
  void skip_empty_lines()
  {
    if(i!=ie && j==end(*i)) // обязательно нужны проверки на валидность i: здесь...
    {
      do { ++i; } while(i!=ie && begin(*i)==end(*i)); //... здесь, ...
      if(i!=ie) j = begin(*i); //... и здесь.
    }
  }
  bool operator==(jagged_iterator const& other) const
  {
    // равенство тоже более хитрое
    return i==other.i && (i==ie ? other.i==other.ie || j==other.j);
  }

  decltype(*j) operator* () const { return *j; }
};

Наш итератор удовлетворяет разумному требованию: он либо указывает на букву, либо стоит на конце текста.
Встать куда-то между буквами он не может, хотя аргументы его конструктора вполне могут указать на такую позицию: начало любой пустой строки, или конец какой-нибудь непустой.
То есть, мысленные переводы строк мы выкинули.

Сделать это как-либо проще — не получится. Ну, разве что заменить итератор конца текста iend на количество строк до конца.




К этому же решению можно было подойти, если взять двухэтажный цикл и развернуть его
for(I i=i0; i!=iend; ++i)
  for(J j=begin(*i); j!=end(*i); ++j)
    process(*j);

Разворачиваем:
I i;
J j;

Li0:
    i=i0;
    goto Li2;
Li1:
    ++i;
Li2:
    if(i==iend) goto Li3;
Lj0:
        j=begin(*i);
        goto Lj2;
Lj1:
        ++j;
Lj2:
        if(j==end(*i)) goto Lj3;
Lp0:
            process(*i);
Lp1:
            goto Lj1;
Lj3:
        goto Li1;
Li3:
    ;

А теперь замечаем, что инициализация итератора — это код, выполняемый от Li0 до Lp0, разыменование — Lp0..Lp1, и инкремент — Lp1..Lp0.
Разыменуемый итератор пребывает в метке Lp0; итератор конца — в метке Li3.
То есть, наш итератор — это кортеж <i,j,is_Lp0_or_Li3,iend>.
Как ни крути, значение iend мы должны запомнить, потому что используем его в процедуре инкремента.
Флаг is_Lp0_or_Li3 принимает значение Lp0 при i!=iend и Li3 при i==iend, поэтому мы можем его не хранить, а подразумевать.

К сожалению, этот итератор неполноценен: он всегда стартует с начала текста и всегда завершается на конце текста.
Добавление возможности стартовать и завершаться в произвольной позиции — приведёт нас к тому, что я написал выше.
Перекуём баги на фичи!
Re: Итератор для vector<vector<T> >
От: c-smile Канада http://terrainformatica.com
Дата: 12.01.14 19:27
Оценка:
Здравствуйте, LightGreen, Вы писали:

Disclaimer: немного не в тему, хотя...

Всегда считал что концепция итераторов в namespace std вещь весьма убогая. Во всяком случае ограниченная сугубо векторами и непрерывными ranges.
Одна из проблем как раз и озвучена в данном топике — не всегда существует понятие end() как comparable сущности.

Вот пример генератора для данной задачи как альтернативы:


#include "generator.h"

template<typename T> 
   $generator(all_elements) 
 {
    size_t r,c;
    vector<vector<T>> &cont;

    all_elements(vector<vector<T>> &c): cont(c), r(0),c(0) {}

    $emit(T)

      for(; r < cont.size(); ++r)
        for( c = 0; c < cont[r].size(); ++c )
          $yield( cont[r][c] );

    $stop;
};


И пример его использования, считаем сумму элементов

  vector<vector<int>> ints2d; 
  ...

  int sum = 0;
  all_elements<int> all(ints2d);

  for(int el; all(el);) 
    sum += el;

  cout << sum << endl;


generator.h из моей статьи
http://www.codeproject.com/Articles/29524/Generators-in-C

Полный пример

  Скрытый текст
#include <vector>
#include <iostream>
#include "generator.h"

using namespace std;

template<typename T> 
   $generator(all_elements) 
 {
    size_t r,c;
    vector<vector<T>> &cont;

    all_elements(vector<vector<T>> &c): cont(c), r(0),c(0) {}

    $emit(T)
      for(; r < cont.size(); ++r)
        for( c = 0; c < cont[r].size(); ++c )
          $yield( cont[r][c] );
    $stop;
};


int main()
{
  vector<vector<int>> ints2d;
  ints2d.resize(2); ints2d[0].resize(2); ints2d[1].resize(2);
  ints2d[0][0] = 0; ints2d[0][1] = 1; ints2d[1][0] = 2; ints2d[1][1] = 3; 

  int sum = 0;
  all_elements<int> all(ints2d);

  for(int el; all(el);) 
    sum += el;

  cout << sum << endl;
  
    return 0;
}
Re[2]: Итератор для vector<vector<T> >
От: Кодт Россия  
Дата: 12.01.14 21:17
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Всегда считал что концепция итераторов в namespace std вещь весьма убогая. Во всяком случае ограниченная сугубо векторами и непрерывными ranges.

CS>Одна из проблем как раз и озвучена в данном топике — не всегда существует понятие end() как comparable сущности.

Ну уж прямо не существует end. Он даже для istream_iterator существует, — как проективная точка +оо.
Если мы, инкрементируя итератор, диагностировали, что дальше некуда, — переводим его в сигнальное состояние.
Все сигнальные состояния между собой считаем неразличимыми.
Сложность только в том, что итератор получается однонаправленный, нельзя из конца отмотать. Ну так и генераторы — тоже однонаправленные.
Перекуём баги на фичи!
Re: Итератор для vector<vector<T> >
От: Evgeny.Panasyuk Россия  
Дата: 12.01.14 22:38
Оценка: 93 (3)
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу.


А какой итератор нужен? Input/Forward/Bidirectional?

Варианты:
1. Делать "в лоб". Тут придётся вручную кодировать автомат.
2. Сделать не InputIterator, а что-то range'е подобное — TrivialInputIterator:
TrivialInputIterator x;
bool{ empty(x) };
++x;
*x;
Так получается немного проще.
Такой итератор автоматически конвертируется в InputRange (по мотивам реализации Eric Niebler'а).
// NOT TESTED!
template<typename Outer>
class JaggedIterator // Models TrivialInputIterator
{
    Outer first, last;
    using Inner = decltype(begin(*first)); // TODO: use iterator_traits
    Inner inner_first;
    using value_type = decltype(*inner_first);
    
    void skip_empty()
    {
        assert(first != last);
        while(inner_first == end(*first))
        {
            ++first;
            if(first == last) break;
            inner_first = begin(*first);
        }
    }
public:
    JaggedIterator(Outer first, Outer last)
        : first(first), last(last)
    {
        if(first != last)
        {
            inner_first = begin(*first);
            skip_empty();
        }
    }
    JaggedIterator &operator++()
    {
        assert(inner_first != end(*first));
        ++inner_first;
        if(first != last)
            skip_empty();
        return *this;
    }
    const int &operator*() const
    {
        return *inner_first;
    }
    friend bool empty(const JaggedIterator &x)
    {
        return x.first == x.last;
    }
};

Использование:
int main()
{
    vector<vector<int>> x = {{}, {}, {1,2,3}, {}, {4, 5, 6}, {7,8}, {9}};

    for(auto x : make_range(make_jagged(begin(x), end(x))))
        cout << x << " ";
}

<b>LIVE DEMO</b>
  FULL CODE
// Refer http://ericniebler.com/2013/11/07/input-iterators-vs-input-ranges/ ,
//       http://boost.2283326.n4.nabble.com/range-fat-iterators-td4654575.html

#include <boost/iterator/iterator_facade.hpp>
#include <type_traits>
#include <iterator>
#include <iostream>
#include <cassert>

using namespace boost;
using namespace std;

template<typename TrivialInputIterator>
class InputRangeAdaptor
{
    mutable TrivialInputIterator it;
    // Cache during increment?
    // Or maybe underlying iterator/range should do it by himself.

    // iterator_traits?
    using reference = decltype(*it);
    using value_type = typename std::remove_reference<reference>::type;
public:
    class iterator: public iterator_facade
                    <
                        iterator, value_type, input_iterator_tag, reference
                    >
    {
        const InputRangeAdaptor *range = nullptr;
    public:
        iterator() = default;
        explicit iterator(const InputRangeAdaptor &range)
            : range{empty(range.it) ? nullptr : &range}
        {}
    private:
        friend class boost::iterator_core_access;
        void increment()
        {
            ++range->it;
            if(empty(range->it))
                range = nullptr;
        }
        bool equal(iterator x) const
        {
            return range == x.range;
            // Another option: move checks for emptiness here from increment.
            // That would be faster for cases when we do not check for equality,
            //   i.e. std::copy_n.
            // But such equality check will be more complicated and slower
        }
        reference dereference() const
        {
            return *range->it;
        }
    };

    explicit InputRangeAdaptor(TrivialInputIterator it)
        : it(it)
    {}
    
    iterator begin() const
    {
        return iterator{*this};
    }
    iterator end() const
    {
        return iterator{};
    }
};

template<typename TrivialInputIterator>
InputRangeAdaptor<TrivialInputIterator> make_range(TrivialInputIterator x)
{
    return InputRangeAdaptor<TrivialInputIterator>{x};
}

/*****************************************************************************/

class NullTerminated  // Models TrivialInputIterator
{
    const char *position;
public:
    explicit NullTerminated(const char *position)
        : position{position}
    {}
    NullTerminated &operator++()
    {
        ++position;
        return *this;
    }
    const char &operator*() const
    {
        return *position;
    }
    friend bool empty(const NullTerminated &x)
    {
        return *x == 0;
    }
};

/*****************************************************************************/

#include <initializer_list>
#include <iterator>
#include <cassert>
#include <vector>

// NOT TESTED!
template<typename Outer>
class JaggedIterator // Models TrivialInputIterator
{
    Outer first, last;
    using Inner = decltype(begin(*first)); // TODO: use iterator_traits
    Inner inner_first;
    using value_type = decltype(*inner_first);
    
    void skip_empty()
    {
        assert(first != last);
        while(inner_first == end(*first))
        {
            ++first;
            if(first == last) break;
            inner_first = begin(*first);
        }
    }
public:
    JaggedIterator(Outer first, Outer last)
        : first(first), last(last)
    {
        if(first != last)
        {
            inner_first = begin(*first);
            skip_empty();
        }
    }
    JaggedIterator &operator++()
    {
        assert(inner_first != end(*first));
        ++inner_first;
        if(first != last)
            skip_empty();
        return *this;
    }
    const int &operator*() const
    {
        return *inner_first;
    }
    friend bool empty(const JaggedIterator &x)
    {
        return x.first == x.last;
    }
};

template<typename Outer>
auto make_jagged(Outer first, Outer last)
{
    return JaggedIterator<Outer>{first, last};
}

int main()
{
    using namespace std;
    vector<vector<int>> x = {{}, {}, {1,2,3}, {}, {4, 5, 6}, {7,8}, {9}};

    for(auto x : make_range(make_jagged(begin(x), end(x))))
        cout << x << " ";
    cout << endl;
}
3. Использовать stackful coroutines — получается проще всего, но дорого в runtime:
#include <boost/coroutine/all.hpp>
#include <initializer_list>
#include <iostream>
#include <vector>

int main()
{    
    using namespace std;
    using namespace boost;
    using namespace coroutines;
    typedef coroutine<int> Coro;

    vector<vector<int>> v = {{}, {}, {1,2,3}, {}, {4, 5, 6}, {7,8}, {9}};

    Coro::pull_type coro([&](Coro::push_type &yield)
    {
        for(auto &inner : v)
            for(auto &x : inner)
                yield(x);
    });

    for(auto x : coro) // InputRange
        cout << x << " ";
}

4. Использовать stackless coroutine. Выше уже показали один вариант, другой есть в Boost.Asio.
5. Банальный continuation passing style. Подойдёт если сами итераторы не нужны(они могут быть нужны для zip-а и т.п.), а требуется просто сделать for_each:
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <vector>

template<typename Range, typename F>
F for_each_jagged(Range &r, F yield)
{
    for(auto &inner : r)
        for(auto &x : inner)
            yield(x);
    return yield;
}

int main()
{
    using namespace std;
    vector<vector<int>> v = {{}, {}, {1,2,3}, {}, {4, 5, 6}, {7,8}, {9}};

    for_each_jagged(v, [](auto x)
    {
        cout << x << " ";
    });
}

6. Реализовать генератор на базе рекурсивных продолжений — самый дорогой и неуклюжий вариант.
Нужно скрестить монады List и Generator, добавить DO-сахар и получится:
int main()
{
    vector<vector<int>> outer = {{}, {}, {1,2,3}, {}, {4, 5, 6}, {7,8}, {9}};

    auto generator = make_generator(
        DO(Generator,
            (inner, outer)
            (x, inner)
            (_, yield(x))
        )
    );
    for(auto x : generator)
        cout << x << " ";
    cout << endl;
}
Re[3]: Итератор для vector<vector<T> >
От: c-smile Канада http://terrainformatica.com
Дата: 12.01.14 22:40
Оценка: 6 (1)
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, c-smile, Вы писали:


CS>>Всегда считал что концепция итераторов в namespace std вещь весьма убогая. Во всяком случае ограниченная сугубо векторами и непрерывными ranges.

CS>>Одна из проблем как раз и озвучена в данном топике — не всегда существует понятие end() как comparable сущности.

К>Ну уж прямо не существует end. Он даже для istream_iterator существует, — как проективная точка +оо.

К>Если мы, инкрементируя итератор, диагностировали, что дальше некуда, — переводим его в сигнальное состояние.
К>Все сигнальные состояния между собой считаем неразличимыми.

Извернуться это мы "завсегда могём". Только сколько оно того изврата требует. А инвалидация тех итераторов если контейнер изменился? И т.д.
Мой генератор из примера например stable к этому делу.

К>Сложность только в том, что итератор получается однонаправленный, нельзя из конца отмотать. Ну так и генераторы — тоже однонаправленные.


Да фигня.

template<typename T> 
   $generator(all_elements) 
 {
    size_t r,c;
    vector<vector<T>> &cont;
    bool forward;

    all_elements(vector<vector<T>> &c, bool pforward = true): cont(c), r(0),c(0), forward(pforward) {}

    $emit(T)

    if(forward)
      for(r = 0; r < cont.size(); ++r)
        for( c = 0; c < cont[r].size(); ++c )
          $yield( cont[r][c] );
    else 
      for(r = cont.size() - 1; r < cont.size(); --r)
        for( c = cont[r].size() - 1; c < cont[r].size(); --c )
          $yield( cont[r][c] );

    $stop;
};
Re[3]: Итератор для vector<vector<T> >
От: Erop Россия  
Дата: 13.01.14 03:05
Оценка:
Здравствуйте, Abyx, Вы писали:


A>хотя не, вру. default-constructed итераторы нельзя сравнивать, по кр.мере в MSVC.

A>тогда нужна статическая или глобальная переменная-вектор, чтобы у нее брать end() итератор, вместо default-constructed.


А разве сравнивать итераторы разных контейнеров можно?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Итератор для vector<vector<T> >
От: night beast СССР  
Дата: 13.01.14 06:12
Оценка:
Здравствуйте, Erop, Вы писали:

A>>хотя не, вру. default-constructed итераторы нельзя сравнивать, по кр.мере в MSVC.

A>>тогда нужна статическая или глобальная переменная-вектор, чтобы у нее брать end() итератор, вместо default-constructed.


E>А разве сравнивать итераторы разных контейнеров можно?


сложный вопрос
вроде нельзя.
было предложение на запрет сравнения, но в стандарт это предложение вошло с невнятной формулировкой
Re: Итератор для vector<vector<T> >
От: sokel Россия  
Дата: 13.01.14 07:17
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.

LG>Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():

По хорошему мне кажется надо для этого ссылку на контейнер в итераторе держать. Как то так:

#include <vector>
#include <iostream>
#include <assert.h>

template<class C>
class l2_iterator
{
    typedef typename C::iterator outer_iterator;
    typedef typename C::value_type inner_container;
    typedef typename inner_container::iterator inner_iterator;
public:
    typedef typename inner_iterator::value_type value_type;
    typedef typename inner_iterator::pointer pointer;
    typedef typename inner_iterator::reference reference;
private:
    l2_iterator(C& c, outer_iterator o_it) 
        : container_(&c), o_it_(o_it) 
    {}
public:
    static l2_iterator begin(C& c) { 
        l2_iterator ret(c, c.begin());
        if(!c.empty()) ret.i_it_ = c.begin()->begin();
        return ret;
    }
    static l2_iterator end(C& c) { 
        return l2_iterator(c, c.end());
    }
public:
    l2_iterator() : container_(0) {}
    l2_iterator& operator++() {
        ++i_it_;
        while(i_it_ == o_it_->end())
        {
            ++o_it_;
            if(o_it_ == container_->end())
                break;
            i_it_ = o_it_->begin();
        }
        return *this;
    }
    bool operator ==(const l2_iterator& other) const {
        assert(container_ &&  container_ == other.container_);
        return o_it_ == other.o_it_ && (o_it_ == container_->end() || i_it_ == other.i_it_);
    }
    bool operator !=(const l2_iterator& other) const {
        return !(*this == other);
    }
    reference operator*() const { return *i_it_; }
    pointer operator->() const { return &(*i_it_); }
private:
    C* container_;
    outer_iterator o_it_;
    inner_iterator i_it_;
};


int main( int argc, char* argv[] )
{
    typedef std::vector<int> inner;
    typedef std::vector<inner> outer;
    typedef l2_iterator<outer> iterator;

    outer c;
    c.resize(3);
    for(int i=0;i<10;++i)
        c[i%3].push_back(i);

    for(iterator it=iterator::begin(c); it!=iterator::end(c); ++it)
        std::cout << *it << std::endl;

    return 0;
}
Re[4]: Итератор для vector<vector<T> >
От: Кодт Россия  
Дата: 13.01.14 07:58
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Извернуться это мы "завсегда могём". Только сколько оно того изврата требует. А инвалидация тех итераторов если контейнер изменился? И т.д.

CS>Мой генератор из примера например stable к этому делу.

А смотря как базовые итераторы устроены. Они тоже могут быть устойчивы к изменениям (использовать внутри ссылку на контейнер и смещение от начала).
Правда, для списков это дороговатое удовольствие, не?

К>>Сложность только в том, что итератор получается однонаправленный, нельзя из конца отмотать. Ну так и генераторы — тоже однонаправленные.


CS>Да фигня.


Да не фигня. Дело не в том, чтобы одним кодом покрыть оба порядка обхода, а в том, чтобы реализовать operator-- в дополнение к operator++ .
Перекуём баги на фичи!
Re[5]: Итератор для vector<vector<T> >
От: Erop Россия  
Дата: 13.01.14 08:11
Оценка: +1
Здравствуйте, night beast, Вы писали:

NB>вроде нельзя.

NB>было предложение на запрет сравнения, но в стандарт это предложение вошло с невнятной формулировкой

Если говорить о чистых указателях, то, вроде как, результат сравнения двух указателей внутрь разных блоков памяти не определён, хотя поведение только не специфицированное, то есть упасть не может, но вернуть может что угодно.
В частности, при сегментной модели памяти, вроде бы можно сравнивать только смещения внутри сегментов...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[6]: Итератор для vector<vector<T> >
От: night beast СССР  
Дата: 13.01.14 08:18
Оценка:
Здравствуйте, Erop, Вы писали:

E>Если говорить о чистых указателях, то, вроде как, результат сравнения двух указателей внутрь разных блоков памяти не определён, хотя поведение только не специфицированное, то есть упасть не может, но вернуть может что угодно.

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

тут еще вопрос, насколько это сейчас актуально.
Re[4]: Итератор для vector<vector<T> >
От: Abyx Россия  
Дата: 13.01.14 08:43
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Abyx, Вы писали:



A>>хотя не, вру. default-constructed итераторы нельзя сравнивать, по кр.мере в MSVC.

A>>тогда нужна статическая или глобальная переменная-вектор, чтобы у нее брать end() итератор, вместо default-constructed.


E>А разве сравнивать итераторы разных контейнеров можно?


угу, нельзя.
In Zen We Trust
Re: Итератор для vector<vector<T> >
От: andrew.f  
Дата: 13.01.14 09:33
Оценка:
Здравствуйте, LightGreen, Вы писали:

LG>Подскажите, люди знающие, как грамотно сделать итератор для vector<vector<T> > так, чтобы внешняя семантика оставалась, как у итератора одного контейнера? Нужно обойти все элементы по одному разу. Типы контейнеров могут быть другими, это не принципиально, например vector<list<T> > или list<list<T> >.

LG>Я пробовал делать по-разному, но всё время возникает одна и та же проблема: непонятно, как сравнивать итераторы в конце — что считать за общий end(). Если внешний it1_ упирается в свой end(), то значение внутреннего it2_ оказывается неопределённым. Поэтому появляются трудности с реализацией функции operator==():
LG>
LG>template <class T>
LG>class MyContainer
LG>{
LG>   typedef std::vector<T> Inner;
LG>   typedef std::vector<Inner> Outer;
LG>   Outer contents_;
LG>public:
LG>   class iterator
LG>   {
LG>      friend class MyContainer;
LG>      Outer::iterator it1_;
LG>      Inner::iterator it2_, it2end_;
LG>      bool isEnd_; // <<<<< костыль
LG>      iterator(Outer::iterator it1, bool) : it1_(it1), isEnd(false) {
LG>         it2_ = it1_->begin();
LG>         it2end_ = it1_->end();
LG>      }
LG>      iterator(Outer::iterator it1) : it1_(it1), isEnd(true) {}
LG>   public:
LG>      T& operator*() { return *it2_; }
LG>      iterator& operator++() {
LG>         if(++it2_==it2end_) {
LG>            ++it1_;
LG>            it2_ = it1_->begin();
LG>            it2end_ = it1_->end();
LG>         }
LG>      }
LG>      bool operator==(iterator it) const {
LG>         return it1_==it.it1_ &&
LG>            (isEnd_==it.isEnd_ || it2_==it.it2_);
LG>      }
LG>   };
   
LG>   iterator begin() { return iterator(contents_.begin(), true); }
LG>   iterator end() { return iterator(contents_.end()); }
LG>};
LG>


LG>В этом коде используется костыль в виде дополнительной переменной isEnd_, которая устанавливается в true для конца первого контейнера (когда значения it2_ и it2end_ не определены). Это увеличивает размер данных под итератор и количество операций сравнения в шаге цикла for(;it!=itEnd; ). Можно ли данную задачу решить изящнее, без дополнительной переменной и дополнительного сравнения?


А вообще зачем в операторе сравнения сравнивать два итератора и it1_ и it2_?
По идее достаточно сравнить только it2_ == it.it2_? Не?

Судя по коду в operator++() у тебя отсутствует промежуточная ситуация, когда it1_ валидный, а it2_ == it2end_. Всё потому, что it2_ сразу перепрыгивает на начало следующего диапазона, когда достигается it2end_. То есть it2_ равен неоднозначному it2end_ только в одном состоянии — когда достигается конец контейнера. Неоднозначный он потому, что может содержать один и тот же указатель (в общем зависит от реализации STL, например NULL).
Поскольку, итератор — это указатель, то валидный итератор из одного диапазона не может оказаться равным валидного итератору из другого диапазона.
Получается что твой сборный итератор окажется равным end() тогда, когда it2_ == Inner->back().end() — это и есть end() твоего контейнера.

Остается поиграться c инициализацией этого итератора. Тут и возникает проблема с инициализацией end(), когда контейнер пустой.
НО, проблема ли это? Ты при пустой инициализации, можешь в Inner контейнер сувать пустой Outer. Таким образом наш Inner всегда содержит данные, даже в пустом состоянии, то есть его begin() всегда валидный, а значит всегда есть Inner->back().end(), который и является end()-итератором сборного контейнера.

Хотя, конечно, STL не оговаривает ситуацию сравнения итераторов из разных контейнеров. Но это совсем какой то экзотический STL.
Re[2]: Итератор для vector<vector<T> >
От: Evgeny.Panasyuk Россия  
Дата: 13.01.14 13:48
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Всегда считал что концепция итераторов в namespace std вещь весьма убогая. Во всяком случае ограниченная сугубо векторами и непрерывными ranges.


А мне нравятся STL итераторы.
Кроме InputIterator, все остальные имеют хорошие примеры моделей, для которых итераторы являются эффективной абстракцией:
ForwardIterator — односвязный список
BidirectionalIterator — двусвязный список, UTF-8 строка
RandomAccessIterator — массивы и т.п.
OutputIterator — да хоть ostream_iterator<int>(cout)

InputIterator часто не имеет выраженного last, да и вообще last тут не особо уместен. InputIterator Это скорее адаптация InputRange к существующим алгоритмам, причём адаптация с небольшим overhead'ом.
Для InputRange нужно что-то типа:
TrivialInputIterator it;
bool{ empty(it) };
++it;
*it;
с отдельными алгоритмами.
Но тем не менее, такой TrivialInputIterator всё же частично совместим с STL итераторами/алгоритмами. Например в случае Counted Range [first, n):
TrivialInputIterator first;
std::copy_n(first, n, out);
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.