Давайте предположим, что я хочу написать двунаправленный итератор, который перебирает все ненулевые значения любого контейнера, предоставляя begin() / end() / rbegin() / rend(). Мне пришлось бы переписать operator++(), чтобы пропустить все нули, с которыми он сталкивается. Чтобы убедиться, что он все еще действителен, он должен будет каждый раз проверять end() и rend() контейнера. Что-то в строках следующего:

template<class Container, class Iter> 
struct NonZeroIter: public Iter
{
  Container& c;

  using Parent = Iter;
  using Parent::Parent;
  using iterator_category = std::bidirectional_iterator_tag;

  bool is_valid() const { return *(*this) != 0; }
  bool is_end()   const { return *this == c.end(); }
  bool is_rend()  const { return *this == c.rend(); }

  NonZeroIter(Container& _c, const Iter& _it):
    Parent(_it),
    c(_c)
  { if(!is_end() && !is_valid()) ++(*this); }

  NonZeroIter& operator++()
  {
    if(!is_end()){
      do{
        Parent::operator++();
      } while(!is_end() && !is_valid());
    }
    return *this;
  }

  NonZeroIter& operator--()
  {
    if(!is_rend()){
      do{
        Parent::operator--();
      } while(!is_rend() && !is_valid());
    }
    return *this;
  }

  NonZeroIter& operator++(int) { NonZeroIter tmp(*this); ++(*this); return tmp; }
  NonZeroIter& operator--(int) { NonZeroIter tmp(*this); --(*this); return tmp; }

};

Теперь я хочу сделать обратный итератор для NonZeroIter, используя std::reverse_iterator, но для этого мне придется проверять rend() каждый раз, когда NonZeroIter проверяет end() и наоборот. Есть ли хороший способ (по возможности, избежать накладных расходов) сделать это или мне нужно написать свой собственный соответствующий класс обратного итератора?

0
igel 5 Фев 2020 в 15:59

2 ответа

Одним из возможных решений может быть базовый класс итераторов, где is_end и is_rend являются виртуальными абстрактными функциями.

Затем создайте классы прямого и обратного итераторов, унаследованные от базового класса, и реализующие функции is_end и is_rend, необходимые для каждого типа итераторов.

0
Some programmer dude 5 Фев 2020 в 13:09

Вместо NonZeroIter явной проверки по end() и rend(), конструктор должен проверить направление итерации и выбрать начало (begin() или rbegin()) и конец ({ {X5}} или rend()). Их можно сохранить как локальные переменные и проверить.

Вместо проверки на «rend» в operator--() вы можете проверить на «begin» (индекс, представленный begin(), совпадает с rend() - 1).

Все итераторы стандартных контейнеров основаны на std::reverse_iterator, поэтому вы можете использовать эти знания, чтобы найти направление _it.

Что-то вроде этого:

template<typename T>
struct is_reverse_iterator : std::false_type {};

template<typename T>
struct is_reverse_iterator<std::reverse_iterator<T>> : std::true_type {};

template<class Container, class Iter> 
struct NonZeroIter: public Iter
{
  using Parent = Iter;
  using Parent::Parent;
  using iterator_category = std::bidirectional_iterator_tag;
private:      
  Parent begin, end;

  bool is_valid() const { return *(*this) != 0; }
  bool is_end()   const { return *this == end; }
  bool is_begin()  const { return *this == begin; }

public:
  NonZeroIter(Container& c, const Iter& _it):
    Parent(_it),
    begin(is_reverse_iterator<Parent> ? c.rbegin() : c.begin()),
    end(is_reverse_iterator<Parent> ? c.rend() : c.end()),
  { if (!is_end() && !is_valid()) ++(*this); }

  NonZeroIter& operator++()
  {
    if (!is_end()){
      do{
        Parent::operator++();
      } while(!is_end() && !is_valid());
    }
    return *this;
  }

  NonZeroIter& operator--()
  {
    // Smallest possible value is begin, but you could also make that begin - 1
    if (!is_begin()){
      do{
        Parent::operator--();
      } while(!is_begin() && !is_valid());
    }
    return *this;
  }

  NonZeroIter& operator++(int) { NonZeroIter tmp(*this); ++(*this); return tmp; }
  NonZeroIter& operator--(int) { NonZeroIter tmp(*this); --(*this); return tmp; }

};
3
vll 5 Фев 2020 в 13:52