2

I'm implementing a template class of hash table with keys of type K and values of type V in C++11. All content of this table is stored in std::list <std::pair <K,V> >. Now I want to implement iterator and const_iterator for this table in a such way, that the client could modify only the second element of pair through iterator and couldn't modify any element of pair through const_iterator.

My first idea was just to use std::list <std::pair <const K,V> > as a storage and provide the appropriate iterator's of this list. The problem is when I declare this type of std::list, begin() and end() always return const_iterator, and when I try to convert it to iterator using this method, it doesn't work, because erase in libstdc++ receives only iterator as arguments (it's a bug of libstdc++). So I can't cast const_iterator to iterator.

How can I implement such iterators?

Community
  • 1
  • 1
perlik
  • 93
  • 6
  • possible duplicate of [How to remove constness of const\_iterator?](http://stackoverflow.com/questions/765148/how-to-remove-constness-of-const-iterator) – BartoszKP Aug 02 '14 at 08:25
  • 1
    I [can't reproduce](http://coliru.stacked-crooked.com/a/1a4519ec2b1c1672) your problem. You didn't happen to declare your `begin()` and `end()` `const`, did you? – T.C. Aug 02 '14 at 08:33
  • "I'm implementing a template class of hash table with keys of type K and values of type V in C++11" - one of these already exists, `std::unordered_map`. This already has the property of elements consisting of a constant key and mutable value. – Richard Hodges Aug 02 '14 at 08:44
  • Yes, I declare them with `const` and when I remove `const`, it works now. But why this happenes? I thought that `const` just states that those methods doesn't change the object. Why `list::begin()` returns `const_iterator` in this case? – perlik Aug 02 '14 at 08:47
  • @perlik In a member function declared `const`, the `this` pointer will act as a pointer-to-const. So let's say your class is named `A`, and your list member `m_list`, then when you do `return m_list`, which is the same as `return this->m_list`, `this` will be of type `const A*`, so `m_list` will be treated as a member of a constant object and therefore `const` itself. And a constant `std::list` will always return a `const_iterator` when `begin()` is called. – jogojapan Aug 02 '14 at 09:13
  • Thanks, it's clear for me now. – perlik Aug 02 '14 at 09:37

1 Answers1

0

I assume here that you have good reasons to use std::list<std::pair<K, V>> for storage, and not std::[unoredered_]map<K, V> which already behaves like you say.

The simplest option is to use Boost's transform iterator:

https://godbolt.org/z/MvKezs8rx

#include<cassert>
#include<iostream>
#include<list>

#include<boost/iterator/transform_iterator.hpp>

template<class K, class V>
struct kv {
    auto operator()(std::pair<K, V>& p) const {
        return std::pair<K const&, V&>{p.first, p.second};
    }
};

template<class Container, class N = typename Container::value_type, typename K = typename N::first_type, typename V = typename N::second_type>
auto begin_as_map(Container& c) {
    return boost::make_transform_iterator(c.begin(), kv<K, V>{});
}

template<class Container, class N = typename Container::value_type, typename K = typename N::first_type, typename V = typename N::second_type>
auto end_as_map(Container& c) {
    return boost::make_transform_iterator(c.end() , kv<K, V>{});
}

int main() {

    std::list<std::pair<std::string, int>> storage = { {"house", 5}, {"car", 3} };

    for(auto const& e : storage) {std::cout<< e.first <<" -> "<< e.second << std::endl;}

    auto it = begin_as_map(storage);
    assert(it->first == "house");  // ok, first is readable
    assert(it->second == 5);

//  it->first = "bla";  // error, first is not writable
    it->second = 7;

    std::cout<<" -- "<<std::endl;
    for(auto const& e : storage) {std::cout<< e.first <<" -> "<< e.second << std::endl;}

}
alfC
  • 14,261
  • 4
  • 67
  • 118