3

I have the following class that wraps a C++ map. I'd like to override just the iterator dereference, to return the map's value only instead of the key. Is this possible at all, without having to re-implement the entire std::map iterator (which I should probably avoid as much as possible)?

Here it is:

#include <map>

using std::map;

class X {
    using Type = map<int, double>;
    using const_iterator = typename Type::const_iterator;

  public:

    void insert(int key, double value) {
        my_map[key] = value;
    }

    const_iterator cbegin() const { return my_map.cbegin(); }

    const_iterator cend() const { return my_map.cend(); }

    const_iterator begin() const { return my_map.cbegin(); }

    const_iterator end() const { return my_map.cend(); }

  private:
    Type my_map;
};

int main() {
    X x;
    double i;
    for (const auto& it : x) {
        i = it.second; // works
        i = it; // fails
    }
}
P.W
  • 26,289
  • 6
  • 39
  • 76
darksky
  • 20,411
  • 61
  • 165
  • 254
  • `void insert(int key, double value) {` -- There is a big difference between `map::insert` and `map::operator []`. Your wrapper confuses the two by only supplying `[ ]` to the caller. – PaulMcKenzie Dec 10 '18 at 02:40
  • @PaulMcKenzie I know, thanks for pointing that out but that's besides the point. This is a simple class to demonstrate what I'm looking for. The insert has nothing to do with how my real class looks like. – darksky Dec 10 '18 at 02:54
  • The comment section is for comments, so I commented. – PaulMcKenzie Dec 10 '18 at 02:54

2 Answers2

1

You pretty much do need to implement an entire iterator type to provide new iterator behavior. Luckily, Boost has a couple of tools that can make this much easier: boost::iterator_facade is a tool for creating an iterator type, which takes care of satisfying all the requirement details for the various types of Iterator as defined by the Standard. And for the common case where you want to create an iterator which wraps another iterator, overriding just pieces of its functionality, there's boost::iterator_adaptor.

So you could define your X::const_iterator like this:

#include <map>
#include <boost/iterator_adaptor.hpp>

using std::map;

class X {
    using Type = map<int, double>;
public:
    class const_iterator : public boost::iterator_adaptor<
        const_iterator,                 // Derived iterator type, for CRTP
        typename Type::const_iterator,  // Wrapped iterator type
        const double>                   // Value type
    {
    public:
        const_iterator() {}
    private:
        // Allow X to create X::const_iterator from Type::const_iterator:
        explicit const_iterator(typename Type::const_iterator map_iter)
            : iterator_adaptor(map_iter) {}
        friend X;

        // Define the dereference operation:
        const double& dereference() const
        { return base()->second; }
        // Allow boost's internals to use dereference():
        friend boost::iterator_core_access;
    };

    const_iterator cbegin() const { return const_iterator(my_map.cbegin()); }
};

...

(I intentionally changed the access of the name X::const_iterator from private to public. Someone may want to explicitly name that iterator type.)

darksky
  • 20,411
  • 61
  • 165
  • 254
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Boost has a range adaptor that does this. The iterator type would be `boost::adaptors::select_second_const_range::iterator` – Caleth Dec 10 '18 at 09:21
  • @Caleth do you have an example of how its used? I can't seem to find an example. – darksky Dec 10 '18 at 13:05
  • 1
    @darksky I'd be tempted to just define a `auto values() const { return my_map | boost::adaptors::map_values; }` which you'd use as `for (const auto & d : x.values()) { ... }`, but you could have a data member instead – Caleth Dec 10 '18 at 13:17
  • Yeah the thing is, I'd really like it if it was implemented with `cbegin` and `cend` so my classes that use the map can do a for-range loop over the class X itself without having to call values() or anything. – darksky Dec 10 '18 at 13:21
0

Borrowing from Yakk's answer here you can easily modify it to suite your needs.

template<class T>
T value_of(T t) { return std::move(t); }
template<class K, class V>
V value_of(std::pair<K, V> const& p) { return p.second; }

template<class It>
struct range_t {
    It b;
    It e;
    It begin() const { return b; }
    It end() const { return e; }
};

template<class T>
struct value_t {
    T t;
    void operator++(){ t++; }
    auto operator*() { return value_of(*t); }
    friend bool operator==(value_t const& left, value_t const& right)
    { return left.t == right.t; }
    friend bool operator!=(value_t const& left, value_t const& right)
    { return left.t != right.t; }
};

template<class T>
range_t<value_t<T>> values_over(T b, T e) {
    return {{b}, {e}};
}

template<class C>
auto values_of(C& c) {
    using std::begin; using std::end;
    return values_over(begin(c), end(c));
}

int main() {
    X x;
    double i;
    for (double const& it : values_of(x)) {
        i = it;
    }
}
David G
  • 94,763
  • 41
  • 167
  • 253