7

Often, you have a map like map<string,X> where the key is the name of the mapped value, and you need an API which lets consumers see all the names... to populate a GUI list-box for example. You can build a vector and return it as an API call but this is rather inefficient. You could just return a reference to the map, but then the values are also accessible and you might not want that.

So how could you write a compliant class, KeyIterator, which wraps map and provides standard iterator access to the keys in that map.

e.g:

map<string,X> m= ...
KeyIterator<string> ki(m);
for(KeyIterator<string>::iterator it=ki.begin();it!=ki.end();++it)
 cout << *it;

KeyIterator should be lightweight so you can return it from a method with virtually no overhead.

edit: I'm not sure I explained perfectly, let me give a better use-case (semi-pseudo):

class PersonManager
{
 private:
  map<string,Person> people;
 public:
  //this version has to iterate the map, build a new structure and return a copy
  vector<string> getNamesStandard();

  //this version returns a lightweight container which can be iterated
  //and directly wraps the map, allowing access to the keys
  KeyIterator<string> getNames();
};

void PrintNames(PersonManager &pm)
{
 KeyIterator<string> names = pm.getNames();
 for(KeyIterator<string>::iterator it=names.begin();it!=names.end();++it)
  cout << *it << endl;
}
Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • have KeyIterator hold a pointer to the map, and have an iterator wrapper class to wrap the map's iterators, forwarding calls to ++, ==, --, etc. Should be relatively straightforward, although having it templated on only the key type will require some type erasure magic – Bwmat Oct 05 '11 at 20:46
  • 1
    http://stackoverflow.com/questions/681943/how-can-i-get-a-stdset-of-keys-to-a-stdmap this could help – Abhay Oct 05 '11 at 20:51
  • Hey @John: I don't see any point wrapping into another class, seriously. Moreover, looking @ Mark Ransom's & Mooing Duck's answers, I don't see much improvement, still starts @ begin() and ends at end(). – Viet Oct 05 '11 at 23:15

3 Answers3

3
template<typename iterator_type>
class KeyIterator
{
    iterator_type iterator;
public:
    typedef typename std::iterator_traits<iterator_type>::value_type::first_type value_type;
    KeyIterator(iterator_type i) : iterator(i) {}
    value_type operator*() { return iterator->first; }
    KeyIterator & operator++() { ++iterator; return *this; }
    bool operator!=(const KeyIterator & right) const { return iterator != right.iterator; }
    // ...
};

Edit: After seeing your edit I realize this isn't exactly what you asked for. You confused me by calling your class a KeyIterator, a more appropriate name would be KeyContainer. You won't be able to template it just on the key type, since it's going to have to contain some kind of reference to the map; you'll need the full definition of the map.

Your request overcomplicates the problem because you must define two different types, KeyIterator and KeyIterator::iterator.

Here's your sample code using my class:

class PersonManager
{
private:
    map<string,Person> people;
public:
    //this version has to iterate the map, build a new structure and return a copy 
    vector<string> getNamesStandard(); 

    //this version returns a lightweight container which can be iterated 
    //and directly wraps the map, allowing access to the keys 
    KeyIterator<map<string,Person>::iterator> getNamesBegin(); 
    KeyIterator<map<string,Person>::iterator> getNamesEnd(); 
}; 

void PrintNames(PersonManager &pm) 
{ 
    KeyIterator<map<string,Person>::iterator> it = pm.getNamesBegin();
    KeyIterator<map<string,Person>::iterator> end = pm.getNamesEnd();
    for(it; it!=end; ++it) 
        cout << *it << endl; 
}
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Well that's the iterator itself, but don't we need a container so we can loop over the contents from `begin()` to `end()`? – Mr. Boy Oct 05 '11 at 20:52
  • 1
    `I wish the map iterator had typedefs for the types it points to...` You mean like `std::iterator_traits::value_type`? – Mooing Duck Oct 05 '11 at 20:55
  • @MooingDuck, thank you! I was expecting a typedef on the iterator type itself, forgot completely about `iterator_traits` (I've never used it). – Mark Ransom Oct 05 '11 at 20:57
  • @John, you just use `KeyIterator(mymap.begin())` or `KeyIterator(mymap.end())`. – Mark Ransom Oct 05 '11 at 20:58
  • @MarkRansom - I updated my question, either I misunderstand your answer or the question was badly explained. – Mr. Boy Oct 06 '11 at 11:20
  • @John: Mark's answer is basically like @Mooing Duck's, but templatized on the iterator type, not the map type. They both work for what you want, but you need to expose *what* the second type of the map is. Though, the user will have no access to it: `KeyIterator::iterator> getNames() const;`. Now the user knows that the strings are mapped to data type `X`, but have only access to the strings themselves. @Duck's version would look like `KeyIterator > getNames() const;`. Not much difference. :) – Xeo Oct 06 '11 at 13:06
  • @John: Also, what you what is not only a simple iterator, you want an iterator *range*. An iterator itself is just a position inside a range, it knows nothing of `begin` and `end`. A range knows that. Or you need to return a `std::pair` of the iterator examples given here, `first` being the beginning and `second` being the end. – Xeo Oct 06 '11 at 13:09
  • Yes I realized this confusion - couldn't think of a proper name for the container class. `KeyContainer` or `KeyReader` perhaps. – Mr. Boy Oct 07 '11 at 08:54
3
#include <map>
#include <string>
#include <iterator>

template <class map>
class KeyIterator { 
    typename map::const_iterator iter_;
public:
    KeyIterator() {}
    KeyIterator(typename map::iterator iter) :iter_(iter) {}
    KeyIterator(typename map::const_iterator iter) :iter_(iter) {}
    KeyIterator(const KeyIterator& b) :iter_(b.iter_) {}
    KeyIterator& operator=(const KeyIterator& b) {iter_ = b.iter_; return *this;}
    KeyIterator& operator++() {++iter_; return *this;}
    KeyIterator operator++(int) {return KeyIterator(iter_++);}
    const typename map::key_type& operator*() {return iter_->first;}
    bool operator==(const KeyIterator& b) {return iter_==b.iter_;}
    bool operator!=(const KeyIterator& b) {return iter_!=b.iter_;}
};

int main() {
    std::map<std::string,int> m;
    KeyIterator<std::map<std::string,int> > ki;
    for(ki=m.begin(); ki!=m.end(); ++ki)
        cout << *ki;
}

http://codepad.org/4wxFGGNV
Doesn't get much more lightweight than that. However, it requires the iterator to be templated on the map type, instead of the key type, which means you have to give out some implementation details if you were attempting to hide internals.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • I just now noticed this compiled and ran on codepad and I didn't qualify `std::string`. What the? – Mooing Duck Oct 05 '11 at 20:57
  • because codepad inserts all kind of stuff, including `using namespace std`. See http://codepad.org/sKvDs2Et. Unfortunately I can't quite work out how I got the contents of `prelude.h` before. I don't know where it's located, and `cpp` can not be invoked as you can tell. – sehe Oct 05 '11 at 21:32
  • oh... Go ideone instead (also has C# and C++0x and many other nice bits. No boost of course :)) – sehe Oct 05 '11 at 21:33
  • @sehe ideone has boost, but only in plain C++, not C++0x mode. And only header-only boost libraries can be used. – Cubbi Oct 05 '11 at 21:34
  • @Cubbi: thx for the notice. I'm absolutely sure I tried Spirit several times. I'll recheck next time – sehe Oct 05 '11 at 21:38
  • @sehe straight out of the spirit 2.0 tutorial: https://ideone.com/y90LL (2.0 because ideone's boost is 1.39) – Cubbi Oct 05 '11 at 21:45
0

I guess the main problem is that you want only one template argument instead of two: KeyIterator<string> instead of KeyIterator<string, X>.

To achieve that, you might need a technique called type erasure. This will involve dynamic allocation and virtual method calls, though.

If it is the latter, you can just wrap a map<string, X>::const_iterator, make dereferencing return a reference to the key and provide constructor(s) that take map's iterators.

UncleBens
  • 40,819
  • 6
  • 57
  • 90