Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does it make sense to provide only const iterators for a container?

I have a container similar to next one:

class MySpecialContainer
{
    std::vector<std::tuple<InternalType, Type1, Type2>> _vec;
};

where Type1 and Type2 are usable outside the container and InternalType is used only inside the container. To iterate through the elements from outside I'm using a member function similar to next one:

void MySpecialContainer::iterate(std::function<void(const Type1&, const Type2&)> fun)
{
    for(auto& it : _vec)
    {
        fun(std::get<1>(it), std::get<2>(it));
    }
}

As you can see this approach has several limitations, like not being able to iterate on a subrange or not being able to use non mutating std::algorithms.

Considering MySpecialContainer elements are non mutable from outside from logical considerations does it make sense to provide only const_iterator for it?

If the answer if yes for the first question, is it better to...?

  1. separate _vec into 2 containers, one for InternalType and one for std::pair<Type1, Type2>, keep them synchronized and just return const_iterator for second vector

  2. keep the vector as it is now and make a custom iterator that exposes only const Type1 and const Type2

like image 929
Mircea Ispas Avatar asked Aug 21 '14 08:08

Mircea Ispas


2 Answers

Just exposing const iterators is fine. There is even precedence in the standard for this, as std::set effectively does this. Technically, iterator and const_iterator can be different types, but you are not allowed to modify the elements through either type of iterator, as that could break the invariants for set.

like image 148
Nevin Avatar answered Oct 23 '22 02:10

Nevin


One option is to expose iterators that give access to certain fields only of your elements, e.g.:

#include <vector>
#include <tuple>
#include <boost/iterator/transform_iterator.hpp>

struct Type1 {};
struct Type2 {};
struct InternalType {};

class MySpecialContainer
{
    typedef std::vector<std::tuple<InternalType, Type1, Type2>> Vec;
    Vec _vec;

    struct Extractor
    {
        std::tuple<Type1&, Type2&> operator()(Vec::value_type& t) const {
            return std::tie(std::get<1>(t), std::get<2>(t));
        }

        std::tuple<Type1 const&, Type2 const&> operator()(Vec::value_type const& t) const {
            return std::tie(std::get<1>(t), std::get<2>(t));
        }
    };

public:
    typedef boost::transform_iterator<Extractor, Vec::iterator> iterator;
    typedef boost::transform_iterator<Extractor, Vec::const_iterator> const_iterator;

    iterator begin() { return iterator{_vec.begin()}; }
    iterator end() { return iterator{_vec.end()}; }
    const_iterator begin() const { return const_iterator{_vec.begin()}; }
    const_iterator end() const { return const_iterator{_vec.end()}; }
};

int main() {
    MySpecialContainer c;
    for(auto x : c) {
    }
}

Note that through non-const iterators you can still update the exposed values because the trasform iterator returns a tuple of references.

like image 31
Maxim Egorushkin Avatar answered Oct 23 '22 02:10

Maxim Egorushkin