9

I am looking for a simple way to create an iterator for the values of a map in C++11.

This method should be simple and transparent: simple in that it should be easy to implement, and transparent in that the client should not know the values come from a map, not a set.

This question has been asked several times before. Many of these questions predate C++11 and use boost, which I do not want to use. Some are not simple, John Ahlgren's solution here, http://john-ahlgren.blogspot.com/2013/10/how-to-iterate-over-values-of-stdmap.html , for example requires a page of code to write a custom iterator.

The others are not transparent, i.e., clearly one can write:

map<string,foo> mymap;
for (auto it=mymap.begin();it!=mymap.end();++it){
  Foo val= it->second;
  ...
 }

However, I do not want to do this because I do not want the client to have to know of the data representation.

The problem comes up as follows.

I have a bunch of objects uniquely indexed with a long "key". Sometimes I want to manipulate sets of these objects. Other times I want to retrieve an object given its key.

I cannot use the "set" class directly for several reasons, chief among which is that it does not store mutable instances, and these instances must be mutable (except, obviously, for the key).

So, I have decided to store all my objects in a giant global hashtable:

map<long,Foo> all_the_objects;

I then do not work with set<Foo> at all. Instead I work with set<long> and use an adaptor to simulate a set of Foo, i.e.,

class SetOfFoo{
  private: set<long> theKeys;
  public:
    void insert(const & Foo);
    size_t size() return theKeys.size();
    bool is_member(const & Foo)
      {return theKeys.find(Foo.key)
          != theKeys.end;}
    Foo & insert(const & Foo val){
       long key=val.key;
       all_the_objects[key]=val;
       return all_the_objects[key];
     }
    ...::iterator begin() {???}
}

In other words, the client of the SetOfFoo class does not know or need to know that SetOfFoo is implemented as as set of keys.

I also cannot just make a Vector myself in the adaptor class, because one cannot store references in C++ collections.

Is it really impossible to make a simple, transparent way to iterate over map<> values? I find it hard to believe, as this is a very common need and is trivial to do in every language I have seen that has hashtables. I just don't understand how this can be hard.

kdog
  • 1,583
  • 16
  • 28
  • 4
    If I were doing this I'd use `for(auto const& value: mymap | boost::adaptors::map_values) {/*...*/}`. This uses [boost::adaptors::map_values](http://www.boost.org/doc/libs/1_55_0/libs/range/doc/html/range/reference/adaptors/reference/map_values.html) from `Boost.Range`. – Mankarse May 12 '14 at 09:56
  • 1
    Side note: `std::map` uses a tree representation, **not** a hash table. If you really need O(1) lookup, use a `std::unordered_map`. – lethal-guitar May 12 '14 at 09:59
  • 2
    I don't want to use Boost. I thought the STL supplanted Boost, anyway, I already have multiple huge stuff in this code, I have more than enough trouble compiling and linking it as is. I am not going to add a whole new library for some trivial thing like this! – kdog May 12 '14 at 09:59
  • 1
    You can store references in c++ collections using a wrapper class `std::reference_wrapper`. – Blaz Bratanic May 12 '14 at 10:00
  • 5
    So you want a simple, easy, universal solution. Someone wrote a simple, easy universal solution, but you don't want to use it... – Kerrek SB May 12 '14 at 10:01
  • Also, doesn't the fact that this is already in Boost shows this is a common issue. How can the STL have things like "hints to the hash table" and other obscurities, and not this utterly fundamental function of a map (or, thanks lethal-guitar, a std::unordered_map). – kdog May 12 '14 at 10:02
  • You could also use a `set` but make the relevant class members `mutable`, or you could store your data in sorted order in a `vector` to allow for efficient lookup... – Kerrek SB May 12 '14 at 10:03
  • Kerrek, I do not know what you mean. – kdog May 12 '14 at 10:04
  • I cannot use a sorted vector as, among other things, insert has to be fast. And I cannot make the other class members mutable, as that would destroy every use of const in the code. – kdog May 12 '14 at 10:06
  • OK the reference_wrapper idea might work. I am trying to wrap my head around it... – kdog May 12 '14 at 10:11
  • 2
    This is not at all a fundamental function of a map, as it can be implemented on top of the map without any compromise in performance. The ability to provide a hint about where to place an element in a hash table *is* fundamental, since an outside user of the class cannot implement that without modifying the internal details of the class itself. – Benjamin Lindley May 12 '14 at 10:13
  • I looked at reference_wrapper but it does not give the client transparent access to the underlying reference. If the object has a method bar(), say, then a reference_wrapper will not also have that method. – kdog May 12 '14 at 10:24
  • You have to create your own version of a map iterator. It will be very similar to the STL's version, but the reference operator will return a value reference rather than a pair reference. – Richard Hodges May 12 '14 at 11:43
  • I've added an answer with a trivial map_value_iterator implementation. It'll need some work to make it robust, but it does the job for you. – Richard Hodges May 12 '14 at 12:05

2 Answers2

7

it's pretty trivial.

Here's an extremely simplistic version that minimally solves the problem for a map of ints to strings. You can either rewrite for the types you want or templatise it as you wish.

#include <map>
#include <iostream>
#include <iterator>
#include <string>
#include <algorithm>

struct map_value_iterator : public std::map<int, std::string>::const_iterator
{
    map_value_iterator(std::map<int, std::string>::const_iterator src)
    : std::map<int, std::string>::const_iterator(std::move(src))
    {

    }

    // override the indirection operator
    const std::string& operator*() const {
        return std::map<int, std::string>::const_iterator::operator*().second;
    }
};


using namespace std;


int main()
{
    map<int, string> myMap { {1, "Hello" }, { 2, "World" } };

    copy(map_value_iterator(begin(myMap)), map_value_iterator(end(myMap)), ostream_iterator<string>(cout , " "));
    cout << endl;

    return 0;
}

Program output:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
Hello World 
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • This is a very nice idea, much simpler and easier than any of the other solutions I have seen, including the Ahlgren solution. So somehow ++ and ==, which I presume are needed for for loops and possibly even by copy, "just work"? That raises a related question: when are we guaranteed that we can override an operation in a STD class and still have the base class "work right"? (And for that matter, when can we just subclass an STD class, and be guaranteed of this). How do we know that overriding something like * will not break something? Especially as I guess it is not virtual... – kdog May 12 '14 at 18:35
  • Just to be clear, Ahlgren uses an adaptor for the iterator and dispatches everything, rather than a subclass. This takes about 15 times as much code as your solution and is much harder to read - but is there a reason he felt this was necessary? – kdog May 12 '14 at 18:41
  • 1
    There will be some corner cases where my ultra-simple solution will need some additions in order to make the iterator into a 100% properly conforming iterator, but yes in principle there's no problem with subclassing as long as you are clear about what works and what doesn't. For example, the base iterator class does not have a virtual destructor and its `value_type` will be a `std::pair`, so really that should be overridden as well to be a `std::string`. The other way you could do it is through encapsulation, but that's a lot more boilerplate code. – Richard Hodges May 12 '14 at 18:48
4

You can do something like the following (C++98):

#include <iostream>
#include <map>
#include <string>
#include <algorithm>

#include "util/pair_iterator.hpp"

template<class T> inline T const& constify(T& t) { return t; }

int main()
{
    using namespace std;
    using namespace util;

    map<int, string> m;
    m[0] = "alice";
    m[1] = "bob";
    m[2] = "carol";
    m[3] = "dave";
    m[4] = "eve";

    copy(
          over_second(m.begin())
        , over_second(m.end())
        , ostream_iterator<string>(cout, "\n")
        );

    copy(
          over_first(m.begin())
        , over_first(m.end())
        , ostream_iterator<int>(cout, "\n")
        );

    // const iterators check

    copy(
          over_second(constify(m).begin())
        , over_second(constify(m).end())
        , ostream_iterator<string>(cout, "\n")
        );

    copy(
          over_first(constify(m).begin())
        , over_first(constify(m).end())
        , ostream_iterator<int>(cout, "\n")
        );
}

Here is an implementation:

// util/pair_iterator.hpp
#include <iterator>

#include "boost/iterator/transform_iterator.hpp"
#include "boost/type_traits/remove_reference.hpp"
#include "boost/type_traits/is_const.hpp"
#include "boost/mpl/if.hpp"

namespace util {

namespace aux {

template<class T> struct dereference_type
    : boost::remove_reference<typename std::iterator_traits<T>::reference>
{
};

template<class PairT>
struct first_extracter
{
    typedef typename boost::mpl::if_<
          boost::is_const<PairT>
        , typename PairT::first_type const
        , typename PairT::first_type
        >::type result_type;
    result_type& operator()(PairT& p) const { return p.first; }
};

template<class PairT>
struct second_extracter
{
    typedef typename boost::mpl::if_<
          boost::is_const<PairT>
        , typename PairT::second_type const
        , typename PairT::second_type
        >::type result_type;
    result_type& operator()(PairT& p) const { return p.second; }
};

} // namespace aux {

template<class IteratorT>
inline
boost::transform_iterator<aux::first_extracter<typename aux::dereference_type<IteratorT>::type>, IteratorT>
over_first(IteratorT const& i)
{
    typedef aux::first_extracter<typename aux::dereference_type<IteratorT>::type> extracter;
    return boost::transform_iterator<extracter, IteratorT>(i, extracter());
}

template<class IteratorT>
inline
boost::transform_iterator<aux::second_extracter<typename aux::dereference_type<IteratorT>::type>, IteratorT>
over_second(IteratorT const& i)
{
    typedef aux::second_extracter<typename aux::dereference_type<IteratorT>::type> extracter;
    return boost::transform_iterator<extracter, IteratorT>(i, extracter());
}

} // namespace util
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • This is just way too involved: it's transparent, but not simple. I'm new to C++, I guess this is just the culture, a page of somewhat opaque code, four include files, and a third-party library, to do something that every other language with a hashtable can do in a line of code. – kdog May 12 '14 at 10:30
  • 3
    @kdog: Well, there are two functors and two factory functions, the rest is handled by boost transform iterator. Usage is simple: just include one header file. – Maxim Egorushkin May 12 '14 at 10:36
  • Wow. I think I will just use a vector of pointers to Foo and call it a day. Although, it is even obvious that a pointer to a value in a hashtable is valid over the lifetime of the hashtable? I know it's true for references, I just asked that, not sure if for pointers too. – kdog May 12 '14 at 10:40
  • 2
    @kdog Yeah, just don't show your code to your boss and you'll be just fine ) – Maxim Egorushkin May 12 '14 at 10:41
  • @kdog [iterators into hash tables are not invalidated, unless the hash table is rehashed, pointers are not invalidated upon rehashing](http://stackoverflow.com/a/18293070/819272) – TemplateRex May 12 '14 at 10:54