2

I'm writing the iterator classes for a custom container (some hash map class), let's call this container Map.

For a couple of reasons it seems handy to derive iterator from const_iterator and I already did this in some other custom container (an array class), which works great, so I want to stick to this pattern.

But now that leads to the following problem (which is quite general/abstract, I'm sorry):

Whatever const_iterator's critical data member is (it might be a Map reference, or a pointer to Map::Elements, many ways are possible), it needs to be non-const so both const_iterator and the derived iterator can utilize it meaningfully in their methods.

But how can this non-const data member, in a proper way, be initialized when dealing with a const Map object (e.g. calling the method const_iterator Map::Begin( void ) const)?


To illustrate my question bit, I wrote some example code that solves the problem by using const_cast which, I suppose, is bad style:

#include <vector>

template< typename K, typename T >
class Map
{
public:
    class const_iterator
    {
    public:
        const_iterator( Map const & a_Map, int a_Index ) : 
            m_Index( a_Index ),
            m_Map( const_cast< Map & >( a_Map ) )  // Note the use of const_cast
        {
        }
    private:
        Map & m_Map;
        int   m_Index;
    };
    class iterator : public const_iterator
    {
    public:
        T & operator * ( void )
        {
            return m_Map.mElements[ m_Index ];
        }
    };
public:
    const_iterator Begin( void ) const { return const_iterator( *this, 0 ); }
    iterator       Begin( void )       { return       iterator( *this, 0 ); }
private:
    std::vector< T > m_Elements;
};
3
  • 1
    Typical iterators don't actually contain your so-called "critical data member" at all. There is usually no connection between a real-life (release) iterator and the container to which it refers.
    – Kerrek SB
    Commented Dec 12, 2011 at 20:39
  • I know this is probably for academic curiosity but there is a std::map which will probably perform better than a std::vector being wrapped into a custom map.
    – AJG85
    Commented Dec 12, 2011 at 20:40
  • @Kerrek SB You're absolutely right. Anyway this is just an example, the "critical data member" could be anything (for example a pointer to the first Element in m_Elements of the Map that provided the iterator). Whatever approach I use the problem is always the same.
    – Baltram
    Commented Dec 12, 2011 at 20:44

1 Answer 1

2

When I played with this in the past, I convinced myself that inheritance wasn't a good idea. While iterator and const_iterator might share a lot of code, they don't really share much in their interface.

Instead, I used templates to get the code reuse, so that iterator and const_iterator are just two versions of the same template. Provide an appropriate implicit conversion operator.

But if you insist on the inheritance model, and don't want your iterator to have its own non-const reference (i.e. its constructor initializes both its non-const version and the base class's const version), then const_cast is the only real option, I think.

5
  • Thanks! So if const_cast is the only option (besides abandoning inheritance) I can live with it.
    – Baltram
    Commented Dec 12, 2011 at 21:03
  • As an aside, you probably want a pointer member, not a reference member. Think about what it means to have something of type const iterator. e.g. how int *const behaves.
    – user1084944
    Commented Dec 12, 2011 at 22:12
  • As another aside, I wanted to make sure you're aware of the consequences of doing this sort of thing. It may be confusing for others to read your code (or for you to read your code 6 months later). It can defeat optimizations made by the container to make the const version faster than the non-const version. It may also prevent the compiler from doing optimizations as well.
    – user1084944
    Commented Dec 12, 2011 at 22:18
  • What sort of thing do you mean, what's confusing with it and why can it defeat optimizations?
    – Baltram
    Commented Dec 13, 2011 at 5:49
  • I once doubled the performance of a computationally-intensive routine just by getting it fully const correct. Yes, that's a rather extreme case, but it does demonstrate that it does help the compiler out. Also, source code-level optimizations can show up too -- e.g. the const version of dynamic_bitset::operator[] (or the C++98 vector<bool>) is much simpler than the non-const version. One could imagine containers with more significant overhead needed for the non-const version!
    – user1084944
    Commented Dec 14, 2011 at 0:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.