Search code examples
c++iteratorconst-iteratorexplicit-conversion

Converting iterators and const_iterators


General context:

I am trying to build a container that will behave as as wrapper around a multi-dimensional array of run time defined dimensions - in fact the underlying array is of course a 1D array of the total size. The main part is that operator [] returns a wrapper on the sub array.

As containers need iterators, I am currently implementing iterators on that container, both Container::iterator and Container::const_iterator. I try hard to mimic standard container iterators, and they should respect all the requirements for random access and output iterators.

I have already noted the following requirements:

  • a public default constructor
  • (of course copy and move semantics)
  • implicit conversion from an iterator to a const_iterator
  • iterator and const_interator should be comparable

Specific context:

Standard containers iterators provide no conversion at all from a const_iterator to an iterator, because removing constness can be dangerous. I have already searched SO for that problem and found How to remove constness of const_iterator? where answers propose differents tricks to remove constness from an operator. So I now wonder whether I should implement an explicit conversion from a const_iterator to an iterator ala const_cast on pointers.

Question:

What are the risks in implementing an explicit conversion from a const_iterator to a (non const) iterator and how is it different from the solutions from the linked question (copied here for easier reading):

  • using advance and distance (constant time form my random access iterators)

    iter i(d.begin());
    advance (i,distance<ConstIter>(i,ci));
    
  • using erase:

    template <typename Container, typename ConstIterator>
    typename Container::iterator remove_constness(Container& c, ConstIterator it)
    {
        return c.erase(it, it);
    }
    

For references, here is a simplified and partial implementation of my iterators:

// Base for both iterator and const_iterator to ease comparisons
template <class T>
class BaseIterator {
protected:
    T *elt;          // high simplification here...
    BaseIterator(T* elt): elt(elt) {}
    virtual ~BaseIterator() {}

public:
    bool operator == (const BaseIterator& other) {
        return elt == other.elt;
    }
    bool operator != (const BaseIterator& other) {
        return ! operator == (other);
    }
    // other comparisons omitted...

    BaseIterator& add(int n) {
        elt += n;
        return *this;
    }  
};

// Iterators<T> in non const iterator, Iterator<T, 1> is const_iterator
template <class T, int cnst=0, class U= typename std::conditional<cnst, const T, T>::type >
class Iterator: public BaseIterator<T> {
    using BaseIterator<T>::elt;

public:
    using value_type = U;
    using reference = U*;
    using pointer = U&;
    using difference_type = int;
    using iterator_category = std::random_access_iterator_tag;

    Iterator(): BaseIterator<T>(nullptr);
    Iterator(T* elt): BaseIterator<T>(elt) {}

    // conversion from iterator to const_iterator
    template <class X, typename = typename std::enable_if<
        (cnst == 1) && std::is_same<X, T>::value>::type>
    Iterator(const BaseIterator<X>& other): BaseIterator<X>(other) {};

    // HERE: explicit conversion from const_iterator to non const
    template <class X, typename = typename std::enable_if<
        std::is_same<X, T>::value && (cnst == 0)>::type>
    explicit Iterator(const Iterator<X, 1 - cnst>& other): BaseIterator<T>(other) {}

    // partial implementation below
    U& operator *() {
        return *elt;
    }
    U* operator ->() {
        return elt;
    }
    Iterator<T, cnst, U>& operator ++() {
        this->add(1);
        return *this;
    }
};

Solution

  • Both the methods you quote require non-const access to the container, so you can't get access to const underlying elements as non-const.

    What you are suggesting doesn't, so it can be UB [dcl.type.cv]