Постоянные и изменяемые итераторы: тот же алгоритм, другой код

Мой вопрос касается того, как повторно использовать код для алгоритма, который const не знает (может использоваться с постоянными или изменяемыми объектами)?

Возьмем, к примеру, итераторы std::vector.
Есть два класса итераторов, использующих схожие методы: std::vector::iterator и std::vector::const_iterator.

Оба итератора указывают на слоты в векторе или за его пределами (например, std::vector::end()).
Оба они имеют методы увеличения и уменьшения. Основное отличие состоит в том, что const_iterator нельзя использовать для записи в вектор.

Если бы я писал код для итераторов, как бы я мог использовать общие методы iterator и const_iterator, которые не зависели бы от константности операции доступа?

В моем текущем коде я дублирую код для методов for_each и visit из-за разницы в доступности. Цикл for_each — это тот же цикл for, разница в том, что один применяет const_visitor, а другой применяет mutable_visitor.

struct Object;

struct Const_Visitor
{
  // Visit function cannot modified the given object.
  virtual void visit(const Object& o) = 0;
};

struct Mutable_Visitor
{
  // The visit function may modify the given object;
  virtual void visit(Object& o) = 0;
};

struct Container
{
  const unsigned int LIMIT = 16;
  Object obj_container[LIMIT];

  // Apply the read-only (constant) visitor
  // to each object in the container
  void for_each(Const_Visitor& cv) const
  {
    // Note:  this loop management is the same
    // as the loop management for the mutable for_each() method.
    for (unsigned int i = 0; i < LIMIT; ++i)
    {
      cv.visit(obj_container[i]);
    }
  }


  // Apply the read/write (mutable) visitor
  // to each object in the container.
  void for_each(Mutable_Visitor& mv)
  {
    // Note:  this loop management is the same
    // as the loop management for the const for_each() method.
    for (unsigned int i = 0; i < LIMIT; ++i)
    {
      mv.visit(obj_container[i]);
    }
  }
};  

В приведенном выше примере механика одинакова для обеих функций for_each. Меняется только посетитель. Те же слоты в массиве передаются функциям visit.

Это можно было бы немного изменить, используя одного посетителя с двумя методами visit, но фундаментальная проблема все еще существует.

struct Object;

struct Single_Visitor
{
  // Method can't modify the object.
  virtual void visit(const Object& o) = 0;

  // Method may modify the object.
  virtual void visit(Object& o) = 0;
};

struct Container
{
  const unsigned int LIMIT = 16;
  Object obj_container[LIMIT];

  // Apply a visitor to each item in container.
  void for_each(Single_Visitor& sv) const
  {
    for (unsigned int i; i < LIMIT; ++i)
    {
      // Should call the visit method,
      // constant object.
      sv.visit(obj_container[i]);
    }
  }


  // Apply a visitor to each item in container.
  void for_each(Single_Visitor& sv)
  {
    for (unsigned int i; i < LIMIT; ++i)
    {
      // Should call the visit method,
      //   mutable object.
      sv.visit(obj_container[i]);
    }
  }
};

С классом посетителя, который имеет два метода (по сравнению с двумя отдельными классами), методы контейнера for_each по-прежнему имеют ту же механику. Цикл такой же, просто вызывается другой метод.

Итак, есть ли способ иметь один цикл for_each, который вызывает соответствующий посетитель на основе константы?


person Thomas Matthews    schedule 09.11.2014    source источник
comment
Основное отличие состоит в том, что const_iterator нельзя использовать для записи в вектор. Я не уверен, что это уместно, но я думаю, что правильное описание состоит в том, что их тип значения является const-qualified. Они могут иметь изменяемые члены и т.д.   -  person dyp    schedule 10.11.2014
comment
Это шаблонное решение выглядит удовлетворительно? coliru.stacked-crooked.com/a/e81a4a86c4bf0453   -  person zch    schedule 10.11.2014


Ответы (1)


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

struct Container
{
  Object obj_container[LIMIT];

  // C can match either `const Container` or `Container`
  // V can match either `Const_Visitor` or `Mutable_Visitor`
  template<class C, class V>
  static void for_each(C& c, V& v) {
    for (unsigned int i = 0; i < LIMIT; ++i)
    {
      v.visit(c.obj_container[i]);
    }
  }

  void for_each(Const_Visitor& cv) const
  {
    for_each(*this, cv);
  }

  void for_each(Mutable_Visitor& mv)
  {
    for_each(*this, mv);
  }
};
person zch    schedule 09.11.2014
comment
Если вы определяете ее как функцию друга, ее можно даже легко вызвать извне (если вам не нравятся функции-члены;) - хотя для этого требуются приемы ADL, если и функция-член, и функция, не являющаяся членом, должны иметь одно и то же имя. . - person dyp; 10.11.2014