Frank
Frank

Reputation: 66244

How to adapt a set iterator to behave like a map iterator?

I have a class Foo that contains a map and provides begin() and end() functions to iterate over it:

class Foo {
  typedef std::map<int, double> Container;
  typedef Container::const_iterator const_iterator;
  Container c_;
 public:
  const_iterator begin() const { return c_.begin(); }
  const_iterator end() const { return c_.end(); }
  void insert(int i, double d) { c_[i] = d; }
  // ...

};

Now I would like to change it internally from std::map<int, double> to just a std::set<int>, but I don't want to break any client code.

So the double d in the insert function would now just be ignored. And the following code should still be valid, where it->second will now just always be 0.0:

Foo foo;
for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) {
  std::cout << it->first << " " << it->second << std::endl;
}

How can I make these changes in the Foo class?

In other words, how can I provide a Foo::const_iterator that adapts the new internal std::set<int>::const_iterator to behave like the old std::map<int,double>::const_iterator?

UPDATE: The reason I want to get rid of the map is memory efficiency. I have millions of Foo instances and cannot afford to store the double values in them.

Upvotes: 5

Views: 252

Answers (5)

DanDan
DanDan

Reputation: 10562

You can't, not completely. The problem is you are changing your interface, which will always break your clients. I would recommend you create two new functions of newBegin and newEnd (or similar) which has your new behaviour. Your old interface you keep this the same but mark it as depreciated. The implementation of this old interface can use one of the work around described by the others.

Upvotes: 0

Stuart Golodetz
Stuart Golodetz

Reputation: 20656

How about something like this?

#include <iostream>
#include <map>
#include <set>

struct Funky
{
    int first;
    static const double second;

    Funky(int i)
    :   first(i)
    {}
};

const double Funky::second = 0.0;

bool operator<(const Funky& lhs, const Funky& rhs)
{
    return lhs.first < rhs.first;
}

class Foo
{
private:
    //std::map<int,double> m_data;
    std::set<Funky> m_data;
public:
    //typedef std::map<int,double>::const_iterator const_iterator;
    typedef std::set<Funky>::const_iterator const_iterator;

    const_iterator begin() const
    {
        return m_data.begin();
    }

    const_iterator end() const
    {
        return m_data.end();
    }

    void insert(int i, double d)
    {
        //m_data.insert(std::make_pair(i, d));
        m_data.insert(i);
    }
};

int main()
{
    Foo foo;
    foo.insert(23, 9.0);
    for(Foo::const_iterator it=foo.begin(), iend=foo.end(); it!=iend; ++it)
    {
        std::cout << it->first << ' ' << it->second << '\n';
    }
    return 0;
}

Upvotes: 1

Would using

std::set<std::pair<int, double> >

not be sufficient for this comparability?

Failing that you can always write your own iterator which wraps the std::list iterator and provides first and second members. Basically your operator++ would call operator++ on the real iterator etc. and the de-referencing operator could return either a temporary std::pair (by value) or a reference to a std::pair that lives within the iterator itself (if your legacy code can deal with that).

Update, slightly contrived example, might work depending on your scenario:

#include <iostream>
#include <set>

class Foo {
  typedef std::set<int> Container;
  typedef Container::const_iterator legacy_iterator;
  Container c_;

  // legacy iterator doesn't have a virtual destructor (probably?), shouldn't
  // be a problem for sane usage though
  class compat_iterator : public legacy_iterator {
  public:
     compat_iterator(const legacy_iterator& it) : legacy_iterator(it) {
     }

     const std::pair<int,double> *operator->() const {
        static std::pair<int,double> value;
        value = std::make_pair(**this, 0.0);
        // Not meeting the usual semantics!
        return &value;
     }
  };
 public:
  typedef compat_iterator const_iterator;

  const_iterator begin() const { return c_.begin(); }
  const_iterator end() const { return c_.end(); }

};



int main() {

  Foo foo;
  for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) {
     std::cout << it->first << " " << it->second << std::endl;
  }

}

Upvotes: 2

Luca Martini
Luca Martini

Reputation: 1474

Perhaps you can define a fake_pair class that implements first and second and put a set<fake_pair> inside Foo.

Upvotes: 0

wilhelmtell
wilhelmtell

Reputation: 58715

Perhaps something along the lines of

operator int()(const std::pair<int, double>& p) const {
    return p.first;
}

maybe within some wrapper?

Upvotes: 0

Related Questions