Search code examples
c++c++14code-reuseconst-iterator

Is it possible to reuse usual iterator to build const iterator?


Research

I found this old answer. I'd like to know if the solution is still effective or if there is a new, more effective way to do it.

Background

Lets suppose I have an iterator like below (specifics don't really matter, only that it is huge):

    class inorder_iterator : public std::iterator<std::forward_iterator_tag, token>
    {
        friend syntax_tree;

        node* current_node;
        std::stack<node*> prev_nodes;
        //std::stack<node*> visited_nodes;
        std::map<node*, bool> visited;
    public:
        inorder_iterator();

        inorder_iterator& operator++();
        inorder_iterator operator++(int);

        token& operator*();
        const token& operator*() const;

        token* operator->();
        const token* operator->() const;

        friend bool operator==(const inorder_iterator lhs, const inorder_iterator rhs);
        friend bool operator!=(const inorder_iterator lhs, const inorder_iterator rhs);

    private:
        inorder_iterator(node* current);
        node* find_leftmost_node(node* from);
    };

Problem

Implementations of the member function declarations are of reasonable size, but I would like to reuse the current iterator to reduce code duplication.

Idea

First idea that came to mind is to templatize on node type, so I could pass const node to make it const iterator, but it just sounds fishy

template <typename Node>
//replace every occurrence of node with Node
// and use decltype(node.tk) instead of token everywhere

Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.


Solution

  • A template is probably the only way to avoid code duplication. But I wouldn't leave the type parameter open. I'd simply use a boolean parameter that is fed to a std::conditional in order to determine the type:

    template<bool IsConst> class iter_impl {
      using value_type = std::conditional_t<IsConst, Node const, Node>;
    };
    

    The two iterator types of the container can then be either a couple of aliases, or if you want truly distinct types, a couple of classes that inherit from the template. Like so:

    struct const_iterator : iter_impl<true> {};
    struct iterator : iter_impl<false> {};
    

    The benefit of using two new classes, is that you can define a converting constructor for const_iterator that allows it to be build from the non const iterator. This is akin to the standard library's behavior.

    Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.

    The fact you use a const Node is indeed an implementation detail. But so long as it gives you the documented type behavior (an iterator to a const member of the container) I wouldn't stress too much over this.