21

Is there any way to rename the first and second accessor functions of a map iterator. I understand they have these names because of the underlying pair which represents the key and value, but I'd like the iterators to be a little more readable. I think this might be possible using an iterator adaptor, but I'm not sure how to implement it.

Please note that I can't use boost.

Example of what I mean:

map<Vertex, Edge> adjacency_list;
for(map<Vertex, Edge>::iterator it = adjacency_list.begin();
    it != adjacency_list.end();
    ++it)
{
    Vertex v = it->first;
    //instead I would like to have it->vertex
}
user281655
  • 457
  • 3
  • 11
  • 1
    Amusing: There seems to be a lot of graph-related questions currently: http://stackoverflow.com/questions/1499878/use-a-graph-library-node-network-library-or-roll-my-own, http://stackoverflow.com/questions/1499217/boost-graph-as-basis-for-a-simple-dag-graph – Éric Malenfant Sep 30 '09 at 19:17
  • Why can't you use boost? – Alice Purcell Oct 02 '09 at 18:58

8 Answers8

22

If you're just concerned about readability you could do something like this:

typedef map<Vertex, Edge> AdjacencyList;
struct adjacency
{
    adjacency(AdjacencyList::iterator& it) 
      : vertex(it->first), edge(it->second) {}
    Vertex& vertex;
    Edge& edge;
};

And then:

Vertex v = adjacency(it).vertex;
Georg Fritzsche
  • 93,086
  • 26
  • 183
  • 232
14

You can't rename the members, but you can have some functions to help.

inline Vertex& vertex(map<Vertex, Edge>::iterator& it) {return it->first;}
inline Edge& edge(map<Vertex, Edge>::iterator& it) {return it->second;}

Then, instead of it->vertex like you want, you can do vertex(it)

KeithB
  • 15,563
  • 2
  • 37
  • 44
7

Unfortunately, no. What I usually do is this:

typedef map<Vertex, Edge> AdjacencyList;
typedef AdjacencyList::value_type Vertex_Edge_Pair;

For readability. Inside your loop you can also say

Vertex& current_vertex = it->first;
Edge& current_edge = it->second;
rlbond
  • 59,991
  • 50
  • 166
  • 218
6

Sure, reimplement or wrap iterator, but is it worth the effort? Wouldn't

Vertex& v = it->first;

be easier?

Michael Krelin - hacker
  • 122,635
  • 21
  • 184
  • 169
3

If you don't need the iterator (e.g., a range-based for loop suits your purpose), then as of c++17 you can use structured bindings:

map<Vertex, Edge> adjacency_list;
for( auto & [ vertex, edge ] : adjacency_list )
{
    // do stuff with vertex
}
schester
  • 160
  • 1
  • 2
  • 10
1

I wouldn't recommend really using this, but it does seem to work, at least to the minimum degree of the test program doing what I wanted/expected:

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

template <class T, class U>
struct my_pair : public std::pair<T, U> {
    T const &vertex;
    my_pair(std::pair<T, U> const &x) : std::pair<T, U>(x), vertex(x.first) { }
};

template <class T, class U>
struct my_map : public std::map<T, U> { 
    my_pair<T, U> find(T const &t) { return my_pair<T, U>(*std::map<T,U>::find(t)); }
};

class Vertex { 
    int x;
public:
    Vertex(int v) : x(v) {}
    bool operator<(Vertex const &other) const { return x < other.x; }
    friend std::ostream &operator<<(std::ostream &os, Vertex const &v) { return os << v.x; }
};

int main() { 
    my_map<Vertex, std::string> m;

    m[1] = "This is it";

    my_pair<Vertex, std::string> mp = m.find(1);
    std::cout << mp.vertex << ": " << mp.second;
    return 0;
}
Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
1

I liked KeithB's solution with free functions. However, a more reusable solution might be nice.

What about function objects that access first or second, as you can name the instances anything you like:

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

struct GetFirst
{
    template <class First, class Second>
    First& operator()(std::pair<First, Second>& p)
    {
        return p.first;
    }

    template <class First, class Second>
    const First& operator()(const std::pair<First, Second>& p)
    {
        return p.first;
    }
};

struct GetSecond
{
    template <class First, class Second>
    Second& operator()(std::pair<First, Second>& p)
    {
        return p.second;
    }

    template <class First, class Second>
    const Second& operator()(const std::pair<First, Second>& p)
    {
        return p.second;
    }
};

int main()
{
    typedef std::map<std::string, int> Map;

    Map persons;
    persons["John"] = 20;
    persons["Mary"] = 24;

    //create named accessors
    GetFirst name;
    GetSecond age;

    for (Map::iterator it = persons.begin(); it != persons.end(); ++it) {
        std::cout << name(*it) << " is aging.\n";
        ++age(*it);
    }

    for (Map::const_iterator it = persons.begin(); it != persons.end(); ++it) {
        std::cout << "Name: " << name(*it) << ", age: " << age(*it) << '\n';
    }
}

This is the best I could do. I also tried to make those functors accept the iterator directly, but one way or another this means that the signature will contain dependent names which apparently makes template type deduction impossible (I couldn't find a way to overload GetSecond for iterator/const_iterator even with deferred return type of C++0x).

UncleBens
  • 38,655
  • 6
  • 51
  • 88
0

I've got an evil solution!

#define vertex first
#define edge second

Although as an evil solution it will doubtless cause great trauma and difficult to diagnose compilation problems when you accidentally use those words elsewhere.

Added for completeness.

Can't believe nobody else has suggested this.

Andy Krouwel
  • 1,049
  • 9
  • 16