0

I am implementing a simple iterator for some custom container (based on a list):

template <class T, class Link>
class single_iterator
{
public:

    using iterator_category = std::forward_iterator_tag;

    //A value is not T but T*, because the list is a contaner of elements of type T *.
    using value_type = T *;

    //Required by std::iterator_traits in GCC.
    using difference_type = std::ptrdiff_t;

    using pointer = value_type *;

    using reference = value_type &;

    single_iterator(Link *p) : pCur(p) {}

    T * operator-> () const { return cur(); }

    T * operator* () const { return cur(); }

    single_iterator & operator++ ()
    {
        this->MoveNext();

        return *this;
    }

    single_iterator operator++ (int)
    {
        single_iterator tmp = *this;

        this->MoveNext();

        return tmp;
    }

    bool operator == (const single_iterator & r) const
    {
        return this->link() == r.link();
    }

    bool operator != (const single_iterator & r)  const
    {
        return !(*this == r);
    }

private:

    //! Results in undefined behavior if the iterator is end().
    T * cur() const { return static_cast<T *>(pCur); }

    void MoveNext() { pCur = pCur->next(); }

    Link * link() const { return pCur; }

    Link * pCur;
};

then I declare iterator and const_iterator in my container and implement begin() and end():

template <class T, class Link, class Derived>
class container
{
public:

    using value_type = T *;

    using iterator = single_iterator<T, Link>;
    using const_iterator =  single_iterator<const T, const Link>;

    iterator begin() { return first(); }
    const_iterator begin() const { return first(); }
};

and when I use the iterator like this it does not compile:

#include <iostream>
#include <vector>

struct A
{
    void func()
    {
        container<A>::const_iterator i = m_v.begin();
    }

    container<A> m_v;
};

int main()
{
    A a;

    a.func();

    return 0;
}

because const_interator can't be constructed from iterator.

What is the right way to implement this conversion with a minimal code duplication and defining separate classes for const_iterator and iterator?

See the same code with std::vector.

EDIT1: The code like this compiles:

struct A
{
    operator A() { return *this;}
};

int main()
{
    A a;

    return 0;
}

so it is possible to define type conversion operator by adding const and const_iterator will convert to itself. But it looks a bit strange...

Dmitriano
  • 1,878
  • 13
  • 29
  • A few of the answers in the linked duplicate mention conversion from `iterator` to `const_iterator` but most do not, and the question is much more general than this one. I'm not sure the duplicate is appropriate, but I am uncomfortable removing it, as I have posted an answer and may be biased. – François Andrieux Sep 29 '20 at 19:39
  • @FrançoisAndrieux I thought I added a comment explaining which answers address it in particular, but I don't see it now. In any case, your answer below is great notwithstanding my opinion that the question is a duplicate. – Ami Tavory Sep 29 '20 at 23:27

1 Answers1

2

You can simply provide an implicit conversion operator which converts the non-const version of your iterator to the const version. A short example :

template<class T>
class my_iter
{
public:
    // Non-const iterator can be implicitly converted to const
    operator my_iter<const T>() const {
        // Replace this with however your iterator is constructed
        return {};
    }
};

Edit : This solution does not allow a typical comparison operator to interoperate between const and non-const iterators, which is a problem. A work around is to force the comparison operator to only accept a const iterator as argument. It will implicitly use the conversion operator when passed a non-const iterator. Example :

bool operator==(const my_iter<std::add_const_t<T>> & p_other) const 
{
    // Compare...
}

This still has the down side that the privates of the argument are not accessible. One work around is to make iterator<const T> a friend class of iterator<T> :

#include <type_traits>

template<class T>
class my_iter
{
public:
    // Non-const iterator can be implicitly converted to const
    operator my_iter<const T>() const {
        // Replace this with however your iterator is constructed
        return {};
    }

    bool operator==(const my_iter<std::add_const_t<T>> & p_other) const 
    {
        return state == p_other.state; // Works even if `state` is `private`
    }

private:
    // Some iterator implementation detail
    T * state = nullptr;

    // `iter<T>` and `iter<const T>` are friends of each other
    friend class my_iter<std::remove_const_t<T>>;
    friend class my_iter<std::add_const_t<T>>;
};

Another is to explicitly convert *this to an iterator<const T>& and only actually implement operator== for iterator<T> with iterator<T> where both objects' private members are accessible.

bool operator==(const my_iter<std::add_const_t<T>> & p_other) const 
{
    if constexpr (std::is_same_v<std::decay_t<decltype(p_other)>, my_iter<T>>) {
        // `*this` and `p_other` are both `const T` iterators
        // You may access the private members of both objects here
        return state == p_other.state; // Works even if `state` is `private`
    }
    else {
        // The `p_other` is not the same type as `*this`
        return static_cast<const my_iter<const T> &>(*this) == p_other;
    }
}

Whichever solution is used, it can be repeated for operator<. Fromoperator< you can construct to other comparison operators. For example operator>(a, b) can be implemented as return b < a; and operator==(a, b) can be implemented as return !(a<b) && !(b<a);

François Andrieux
  • 28,148
  • 6
  • 56
  • 87