1

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;
    }
};
5
  • 1
    Why does client code have a const iterator in the first place? It's either because it has a const ref to the container, and should not mutate it, or because it explicitly called cbegin etc. and should just have called begin instead if that's what it wanted. What other use case is there? Commented Jun 15, 2018 at 12:27
  • @Useless: Cannot be sure of it. This container is intended to be used a tool by other programmers and other applications, the reason why I try to be as close as possible of standard containers. I just know that the language allows const_cast to remove constness from a pointer or ref. And I suspect some algorithmic libraries to use and return only const_iterators because they do not change the container, while the user of that library will actually want to change where the returned iterator points to. Commented Jun 15, 2018 at 12:34
  • 2
    The std algorithms mostly return whatever iterator type you passed in - unless there's some specific library you know that does this, I'd disallow the conversion. Commented Jun 15, 2018 at 12:38
  • Both the methods you quote require non-const access to the container, so a const_casting the pointed-to element can't be undefined behaviour. What you are suggesting doesn't, so it can be UB Commented Jun 15, 2018 at 12:40
  • @Caleth Good point! I had read the refd post too quickly because I could find it there... Anyway that is enough to answer my question and I will remove my explicit conversion. If you make it an answser, I will upvote (and probably accept unless someone else finds other reasons) Commented Jun 15, 2018 at 13:01

1 Answer 1

2

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]

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.