Search code examples
c++iteratorconst-iterator

Calls const instead of non-const access operator for iterator


I want to build a custom iterator on top of one of STL iterators. Here is the stripped-down version of the code which I would expect to be compilable:

#include <cstddef>
#include <iterator>
#include <list>

class Data
{
public:
  double x;
};

typedef std::list<Data> list_of_data;

template <class IteratorType>
class my_iterator :
     public std::iterator<std::bidirectional_iterator_tag,
                          typename IteratorType::value_type >
{
public:
  //type of itself
  typedef my_iterator self;
  //type of underlying iterator
  typedef IteratorType list_iterator_type;

  my_iterator(list_iterator_type it) :
    m_it(it)
  {}//constructor.

  my_iterator(const self& source) :
    m_it(source.m_it)
  {}

  self& operator=(const self& source)
  {
    m_it = source.m_it;
    return *this;
  }//operator=

  bool operator==(self other)
  {
    return m_it == other.m_it;
  }

  bool operator!=(self other)
  {
    return m_it != other.m_it;
  }

  inline typename self::reference operator*()
  { return (*m_it);}

  inline const typename self::reference operator*() const
    { return (*m_it); }

  inline typename self::pointer operator->()
    { return &(*m_it); }

  inline const typename self::pointer operator->() const
    { return &(*m_it); }

  inline self& operator++()
  {
    ++m_it;
  }//operator++

  inline self operator++(int)
  {
    self tmp(*this);
    ++(*this);
    return tmp;
  }//operator++(int)

private:

  list_iterator_type m_it;
};

///non constant iterator over cells.
typedef my_iterator<list_of_data::iterator> iterator;
///constant iterator over cells.
typedef my_iterator<list_of_data::const_iterator> const_iterator;

int main()
{
  list_of_data test_list;
  Data a;
  test_list.push_back(a);
  test_list.push_back(a);
  test_list.push_back(a);
  for(const_iterator it  = const_iterator(test_list.begin()); 
                     it != const_iterator(test_list.end()); ++it)
  {
    double x = it->x;
    double y = (*it).x;
  }
}

However it shows the following error message :

test_list.cpp: In instantiation of ‘typename my_iterator<IteratorType>::self::pointer my_iterator<IteratorType>::operator->() [with IteratorType = std::_List_const_iterator<Data>; typename my_iterator<IteratorType>::self::pointer = Data*]’:
test_list.cpp:92:18:   required from here
test_list.cpp:55:21: error: invalid conversion from ‘const Data*’ to ‘std::iterator<std::bidirectional_iterator_tag, Data, long int, Data*, Data&>::pointer {aka Data*}’ [-fpermissive]
     { return &(*m_it); }
                     ^
test_list.cpp: In instantiation of ‘typename my_iterator<IteratorType>::self::reference my_iterator<IteratorType>::operator*() [with IteratorType = std::_List_const_iterator<Data>; typename my_iterator<IteratorType>::self::reference = Data&]’:
test_list.cpp:93:18:   required from here
test_list.cpp:49:18: error: invalid initialization of reference of type ‘std::iterator<std::bidirectional_iterator_tag, Data, long int, Data*, Data&>::reference {aka Data&}’ from expression of type ‘const Data’
   { return (*m_it);}

as far as I can see it refers to non-const version of the access operator, but why, if I explicitly use constant iterator here?


Solution

  • After some investigation I finally found the problem, so I am posting the correct, compilable, version of the above code. Here are the highlights:

    -- The main problem was that I under-defined template parameters for base std::iterator class. It takes 5 parameters, but I defined only first 2, relying on default type assignment for the rest. This was a mistake. For example by default pointer is defined as value_type*, but what I needed was actually const value_type* for constant iterator. I therefore defined all 5 template parameters.

    -- There were no need to have 2 versions of operator*() and operator->().

    -- I also added another templated constructor to allow assigning non-const iterator to the const ones.

    So below are results of my endeavours.

    #include <cstddef>
    #include <iostream>
    #include <iterator>
    #include <list>
    
    class Data
    {
    public:
      double x;
    };
    
    typedef std::list<Data> list_of_data;
    
    template <class IteratorType>
    class my_iterator :
         public std::iterator<std::bidirectional_iterator_tag,
                              typename IteratorType::value_type,
                              typename IteratorType::difference_type,
                              typename IteratorType::pointer,
                              typename IteratorType::reference>
    {
    public:
      //type of itself
      typedef my_iterator self;
      //type of iterator over cells
      typedef IteratorType list_iterator_type;
    
      my_iterator(list_iterator_type it) :
        m_it(it)
      {}//constructor.
    
      my_iterator(const self& source) :
        m_it(source.m_it)
      {}
    
      template<class another_iterator>
      my_iterator(const my_iterator<another_iterator>& source) :
        m_it(source.m_it)
      {}
    
      self& operator=(const self& source)
      {
        m_it = source.m_it;
        return *this;
      }//operator=
    
      bool operator==(self other) const
      {
        return m_it == other.m_it;
      }
    
      bool operator!=(self other) const
      {
        return m_it != other.m_it;
      }
    
      inline typename self::reference operator*() const
      { return (*m_it);}
    
      inline typename self::pointer operator->() const
        { return &(*m_it); }
    
      inline self& operator++()
      {
        ++m_it;
        return (*this);
      }//operator++
    
      inline self operator++(int)
      {
        self tmp(*this);
        ++(*this);
        return tmp;
      }//operator++(int)
    
    private:
    
      list_iterator_type m_it;
    };
    
    ///non constant iterator over cells.
    typedef my_iterator<list_of_data::iterator> iterator;
    ///constant iterator over cells.
    typedef my_iterator<list_of_data::const_iterator> const_iterator;
    
    int main()
    {
      list_of_data test_list;
      Data a;
      test_list.push_back(a);
      test_list.push_back(a);
      test_list.push_back(a);
      for(iterator it  = iterator(test_list.begin()); 
                         it != iterator(test_list.end()); ++it)
      {
        it->x = 2;
      }
      for(const_iterator it  = const_iterator(test_list.begin()); 
                         it != const_iterator(test_list.end()); ++it)
      {
        std::cout << "  it->x =" << it->x << std::endl;
        std::cout << "(*it).x =" << (*it).x << std::endl;
      }
    }