Transform an iterator to unordered_map with a value type of an iterator pointer on the same map as a Const reference value type

I have the following class:

#include <unordered_map> #include <memory> class Node { public: typedef std::unique_ptr<Node> ptr_type; typedef std::unordered_map<char, ptr_type> map_type; typedef /**???**/ const_iterator; const_iterator begin() const; const_iterator end() const; private: map_type _children; }; 

As you can see, I want the user of this class to be able to _children over _children elements without being able to modify them. That's why I want to create an iterator that points to elements like pair<char, const Node&> instead of pair<char, ptr_type> .

Creating an iterator base class seems too complicated for this task. I took a look at raising an iterator, I think transform_iterator might be the way to go, but I have not yet found how to make it work.

While I am in it, does anyone know where I can find examples of different examples of iterators defined in boost-iterators ? There is only one example in each document, and they do not always correspond to my needs (I am new to this library, I may have missed something obvious).

UPDATE Here is my attempt to use boost::transform_iterator

 class Node { public: typedef std::unique_ptr<Node> ptr_type; typedef std::unordered_map<char, ptr_type> map_type; struct Transformer { std::pair<char, const Node&> operator()(const std::pair<char, ptr_type> &p) const { return std::pair<char, const Node&>(p.first, *p.second); } }; typedef boost::transform_iterator<Transformer, map_type::const_iterator, std::pair<char, const Node&>&, std::pair<char, const Node&>> const_iterator; const_iterator begin() const { return boost::make_transform_iterator<Transformer, map_type::const_iterator>(_children.begin(), Transformer()); } const_iterator end() const { return boost::make_transform_iterator<Transformer, map_type::const_iterator>(_children.end(), Transformer()); } private: map_type _children; }; 

Unfortunately, it does not compile and gives the following error:

 error: no type named 'type' in 'boost::mpl::eval_if<boost::is_same<boost::iterators::use_default, boost::iterators::use_default>, boost::result_of<const Node::Transformer(const std::pair<const char, std::unique_ptr<Node> >&)>, boost::mpl::identity<boost::iterators::use_default> >::f_ {aka struct boost::result_of<const Node::Transformer(const std::pair<const char, std::unique_ptr<Node> >&)>}' typedef typename f_::type type; 
+5
source share
2 answers

If using boost-iterator is optional, you can write your own iterator. I am sending a message that satisfies ForwardIterator . You can deploy it to the BidirectionalIterator trivially (this can be a bit tedious, however).

Before sending it, I am afraid that I will not be able to fulfill your requirements (in addition to using boost-iterator); std::pair<char, const Node*> used instead of std::pair<char, const Node&> , because the latter prohibits copying. Perhaps this prevents you from compiling your boost::transform_iterator example (I'm not sure, I'm not so familiar with the boost iterator).

Anyway, here is code.cpp (125 lines). main for testing included:

 #include <unordered_map> #include <memory> class Node; template <class Map> class MyIterator { public: // iterator member typedefs using iterator_category = std::forward_iterator_tag; using value_type = std::pair<char, const Node*>; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = value_type&; // typedef for underlying iterator using underlying_iterator = typename Map::const_iterator; // constructors // takes an underlying iterator explicit MyIterator(underlying_iterator it) : _it(std::move(it)) {} // default constructor; required by ForwardIterator MyIterator() = default; // dereference; required by InputIterator reference operator*() { update(); return _p; } // dereference; required by InputIterator pointer operator->() { update(); return &_p; } // increment; required by Iterator MyIterator<Map>& operator++() { ++_it; return *this; } // increment; required by InputIterator MyIterator<Map> operator++(int) { auto mit = *this; ++*this; return mit; } // comparison; required by EqualityComparable bool operator==(const MyIterator<Map>& mit) const { return _it == mit._it; } // comparison; required by InputIterator bool operator!=(const MyIterator<Map>& mit) const { return !(*this == mit); } private: // this method must be called at dereference-time but not // traverse-time in order to prevent UB at a wrong time. void update() { _p = value_type{_it->first, &*(_it->second)}; } // the underlying iterator that tracks the map underlying_iterator _it; // the pair of the desired type. without it, eg operator-> doesn't // work; it has to return a pointer, and the pointed must not be a // temporary object. value_type _p; }; class Node { public: typedef std::unique_ptr<Node> ptr_type; typedef std::unordered_map<char, ptr_type> map_type; typedef MyIterator<map_type> const_iterator; const_iterator begin() const { return const_iterator{_children.begin()}; } const_iterator end() const { return const_iterator{_children.end()}; } private: map_type _children; // additional members for testing purposes. public: Node(std::string name) : _name(std::move(name)) {} Node(std::string name, map_type&& children) : _children(std::move(children)), _name(std::move(name)) {} std::string const& name() const { return _name; } private: std::string _name; }; #include <iostream> // test program; construct a simple tree and print children. int main() { typedef std::unique_ptr<Node> ptr_type; typedef std::unordered_map<char, ptr_type> map_type; ptr_type leaf1(new Node("leaf1")); ptr_type leaf2(new Node("leaf2")); ptr_type leaf3(new Node("leaf3")); map_type branch; branch.emplace('1', std::move(leaf1)); branch.emplace('2', std::move(leaf2)); branch.emplace('3', std::move(leaf3)); Node parent("parent", std::move(branch)); for (auto it = parent.begin(); it != parent.end(); ++it) { std::cout << it->first << ' ' << it->second->name() << '\n'; } return 0; }; 

compilation command:

 g++ -std=c++11 -g -O2 -Wall code.cpp 

my conclusion:

 3 leaf3 2 leaf2 1 leaf1 

MyIterator written as a template class, so if you want to change std::unordered_map to, for example, std::map , you do not need to change MyIterator ;)

What complicates this is that operator* must return a link to std::pair ; this means that somewhere the < non-temporary ) std::pair object must exist, otherwise the link will become a dangling link. The same goes for operator-> (replace "link" with "pointer").

Here MyIterator::_p is std::pair , the link to which is made. This is assigned to copies during updates that std::pair<char, const Node&> (a pair containing a link) prohibits.

Alternatives to std::pair<char, const Node&> : std::pair<char, const Node*> or std::pair<char, std::reference_wrapper<const Node>> . Replace it->second->name() with it->second.get().name() if you decide to use the alternative std::reference_wrapper .

+4
source

I think this may be the reason for boost::indirect_iterator . Adapting an example from the documentation on acceleration (trivial) map<char, char *> :

 #include <iostream> #include <map> #include <boost/iterator/indirect_iterator.hpp> int main() { char characters[] = "abcdefg"; size_t ncharacters = sizeof characters - 1; char *charptr[ncharacters]; for (size_t i = 0; i < ncharacters; ++i) { charptr[i] = &characters[i]; } std::map <char, char *> map1; for (size_t i = 0; i < ncharacters; ++i) { map1[characters[i]] = charptr[i]; /* Trivial, just to demonstrate */ } boost::indirect_iterator<char * const*, char const> const_indirect_first(charptr), const_indirect_last(charptr + ncharacters); std::copy(const_indirect_first, const_indirect_last, std::ostream_iterator<char>(std::cout, " ")); std::cout << std::endl; return 0; } 
+1
source

All Articles