Home > Back-end >  Trouble implementing const_iterator in c
Trouble implementing const_iterator in c

Time:12-27

I'm struggling trying to implement a const_iterator to my container class, I did a short reproductible example with a basic array to show the problem (I know that std::iterator is deprecated but this is for a project in C 98 standard):

#include <iostream>
#include <cstdlib>
#include <iterator>

template <
    typename T
> struct remove_const { typedef T type; };
template <
    typename T
> struct remove_const < const T > { typedef T type; };

template <
    typename T
> struct add_const { typedef const T type; };

template <
    class T,
    std::size_t size
> class Array {
    public:
        template <
            class U
        > class base_iterator : public std::iterator <
            std::random_access_iterator_tag,
            U
        > {
            public:
                typedef std::iterator <
                    std::random_access_iterator_tag,
                    U
                >   iterator_type;

                base_iterator() : ptr(NULL) { }
                base_iterator(U *p) : ptr(p) { }
                base_iterator(const base_iterator &other) : ptr(other.ptr) { }
                base_iterator(const base_iterator< remove_const< U > > &other) : ptr(other.ptr) { }
                base_iterator(const base_iterator< add_const< U > > &other) : ptr(other.ptr) { }

                base_iterator   &operator =(const base_iterator &other) {
                    ptr = other.ptr;
                    return *this;
                }
                base_iterator   &operator =(const base_iterator< remove_const< U > > &other) {
                    ptr = other.ptr;
                    return *this;
                }
                base_iterator   &operator =(const base_iterator< add_const< U > > &other) {
                    ptr = other.ptr;
                    return *this;
                }
                U               &operator *(void) { return *ptr; }
                U               &operator *(void) const { return *ptr; }
                U               *operator ->(void) { return ptr; }
                U               *operator ->(void) const { return *ptr; }
                base_iterator   &operator   (void) {
                      ptr;
                    return *this;
                }
                base_iterator   operator   (int) {
                    return base_iterator(ptr  );
                }
                base_iterator   &operator --(void) {
                    --ptr;
                    return *this;
                }
                base_iterator   operator --(int) {
                    return base_iterator(ptr  );
                }
                base_iterator   operator  (const typename iterator_type::difference_type &amount) const {
                    return base_iterator(ptr   amount);
                }
                base_iterator   operator -(const typename iterator_type::difference_type &amount) const {
                    return base_iterator(ptr - amount);
                }
                base_iterator   &operator  =(const typename iterator_type::difference_type &amount) {
                    ptr  = amount;
                    return *this;
                }
                base_iterator   &operator -=(const typename iterator_type::difference_type &amount) {
                    ptr -= amount;
                    return *this;
                }
                bool            operator ==(const base_iterator &other) const {
                    return ptr == other.ptr;
                }
                bool            operator !=(const base_iterator &other) const {
                    return ptr != other.ptr;
                }
                bool            operator <(const base_iterator &other) const {
                    return ptr < other.ptr;
                }
                bool            operator <=(const base_iterator &other) const {
                    return ptr <= other.ptr;
                }
                bool            operator >(const base_iterator &other) const {
                    return ptr > other.ptr;
                }
                bool            operator >=(const base_iterator &other) const {
                    return ptr >= other.ptr;
                }
            private:
                U   *ptr;
        };
        typedef base_iterator< T >                      iterator;
        typedef base_iterator< const T >                const_iterator;
        typedef std::reverse_iterator< iterator >       reverse_iterator;
        typedef std::reverse_iterator< const_iterator > const_reverse_iterator;

        iterator                begin(void) { return iterator(data); }
        const_iterator          begin(void) const { return const_iterator(data); }
        iterator                end(void) { return iterator(data   size); }
        const_iterator          end(void) const { return const_iterator(data   size); }
        reverse_iterator        rbegin(void) { return reverse_iterator(data   size - 1); }
        const_reverse_iterator  rbegin(void) const { return const_reverse_iterator(data   size - 1); }
        reverse_iterator        rend(void) { return reverse_iterator(data - 1); }
        const_reverse_iterator  rend(void) const { return const_reverse_iterator(data - 1); }

    private:
        T   data[size];
};

int main(void) {
    Array< int, 20 >    a;

    for (Array< int, 20 >::iterator it = a.begin(); it != a.end();   it)
        *it = 42;
    for (Array< int, 20 >::const_iterator it = a.begin(); it != a.end();   it)
        std::cout << *it << std::endl;
}

the compiler outputs

problem.cpp:106:40: error: no viable conversion from 'base_iterator<int>' to 'base_iterator<const int>'
        for (Array< int, 20 >::const_iterator it = a.begin(); it != a.end();   it)
                                              ^    ~~~~~~~~~
problem.cpp:24:5: note: candidate constructor not viable: no known conversion from 'Array<int, 20>::iterator' (aka 'base_iterator<int>') to 'const int *' for 1st argument
                                base_iterator(U *p) : ptr(p) { }
                                ^
problem.cpp:25:5: note: candidate constructor not viable: no known conversion from 'Array<int, 20>::iterator' (aka 'base_iterator<int>') to 'const Array<int, 20>::base_iterator<const int> &' for 1st argument
                                base_iterator(const base_iterator &other) : ptr(other.ptr) { }
                                ^
1 error generated.

So it seems that the problem is that base_iterator< T > is not convertible to base_iterator< const T >, but firstly I'm wondering why do we need a conversion ? As there are const overloads for begin and end, and operator */->, shouldn't it call the const versions? And secondly if we need a conversion I can't figure out how to do this

EDIT 1: I implemented remove_const and add_const to do the conversion but it still doesn't compile

CodePudding user response:

Your compiler is telling you that it can't convert your iterator to your const_iterator.

a.begin();

Since a is a mutable object, the begin() overload that returns an iterator gets called, but an attempt is made to store the result into a const_iterator, and the way you designed your iterator classes does not allow for that conversion to take place.

This is the same reason why you can't take, for example, a std::vector<int> and assign it, just like that, to std::vector<const int>. It's the same fundamental reason. std::vector<int> and std::vector<const int> are two completely different classes that are not related to each other in any form, or fashion. Your two iterator classes are in the same boat.

What you will need to do is redesign your iterator classes so that this kind of a conversion becomes trivial.

The classical way to handle this is to have iterator inherit from const iterator, something like:

class const_iterator {
protected:

    T *p;  // The underlying pointer, or whatever is being used to reference the iterator value and/or its container.

public:

    // Implement only the `const` methods here
};

class iterator : public const_iterator {

public:

    // Implement only the non-const methods here
};

Now, the magical conversion happens automatically, where needed. Both iterator and const_iterator have full complete access to the underlying container, and its values. const_iterator needs to only be careful enough to preserve const-correctness, i.e. its operator* overload return a const T &, while iterator::operator*() returns a T &, and so on.

CodePudding user response:

Whether or not the const qualified version of the member function is called, depends only on whether the type of the expression on which the member function is called is const qualified.

In a.begin() in your example a is declared as Array< int, 20 > which is not const. So the non-const-qualified version of begin() will be called.

If you want to have your container class compliant with the Container requirement in the C standard library, then you must make your iterator convertible to const_iterator. This is one of the stated requirements and would make your test code work as expected.

For a full list of Container requirements see cppreference.

  • Related