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.