обращение итератора, который зависит от end () - PullRequest
0 голосов
/ 05 февраля 2020

Давайте предположим, что я хочу написать двунаправленный итератор, который перебирает все ненулевые значения любого контейнера, обеспечивая 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() и наоборот. Есть ли хороший способ (по возможности, избежать накладных расходов) сделать это или мне нужно написать свой собственный соответствующий класс обратного итератора?

Ответы [ 3 ]

3 голосов
/ 05 февраля 2020

Вместо NonZeroIter проверки явно по end() и rend(), конструктор должен проверить направление итерации и выбрать начало (begin() или rbegin()) и конец (end() или 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; }

};
1 голос
/ 05 февраля 2020

На основании ответа @vll я получил следующий код:

// these structs compile-time translate begin,end to rbegin,rend for reverse iters
template<class Container, class Iter>
struct BeginEndIters
{
  using iterator     = Iter;
  static iterator begin(Container& c) { return c.begin(); }
  static iterator end(Container& c) { return c.end(); }
};
template<class Container, class Iter>
struct BeginEndIters<Container, std::reverse_iterator<Iter>>
{
  using iterator     = std::reverse_iterator<Iter>;
  static iterator begin(Container& c) { return c.rbegin(); }
  static iterator end(Container& c) { return c.rend(); }
};

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

  // this is the main change
  using BeginEnd = BeginEndIters<Container, Iter>;
  // ^^^^^^^^^^^

  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 == BeginEnd::end(c); }
  bool is_past_begin()  const { return *this == std::prev(BeginEnd::begin(c)); }

  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_past_begin()){
      do{
        Parent::operator--();
      } while(!is_past_begin() && !is_valid());
    }
    return *this;
  }

  NonZeroIter& operator++(int) { NonZeroIter tmp(*this); ++(*this); return tmp; }
  NonZeroIter& operator--(int) { NonZeroIter tmp(*this); --(*this); return tmp; }  
};
0 голосов
/ 05 февраля 2020

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

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

...