views:

368

answers:

3

I'd like to create a std::map that contains a std::vector of iterators into itself, to implement a simple adjacency list-based graph structure.

However, the type declaration has me stumped: it would seem you need the entire map type definition to get the iterator type of said map, like so:

map< int, Something >::iterator MyMap_it;  // what should Something be?
map< int, vector<MyMap_it> > MyMap_t;

Is there some sort of partial map iterator type I can get with just the key type, so I can declare the full map?

+9  A: 

You could use forward declaration of a new type.

class MapItContainers;
typedef map<int, MapItContainers>::iterator MyMap_it;

class MapItContainers
{
public:
 vector<MyMap_it> vec;
};

With this indirection the compiler should let you get away with it. It is not so very pretty but honestly I don't think you can break the self referencing easily.

nasmorn
gcc 4.4.1 appears to not barf on this, it even lets me create instances of `map<int, MapItContainers>`.
Omnifarious
+5  A: 

Not too ugly, considering…

This works in GCC 4.0.1 and compiles fine in Comeau strict mode.

The template definitions are parsed and deferred until they're instantiated. The compiler doesn't even see what a rec_map_iterator is until it's time to create one, by which time it knows how to do so ;v) .

template< class key >
struct rec_map;

template< class key >
struct rec_map_iterator : rec_map< key >::iterator {
    rec_map_iterator( typename rec_map< key >::iterator i)
    : rec_map< key >::iterator(i) {}
};

template< class key >
struct rec_map : map< key, vector< rec_map_iterator< key > > > {};

Here's the test program I used.

#include <iostream>
#include <map>
#include <vector>

using namespace std;

template< class key >
struct rec_map;

template< class key >
struct rec_map_iterator : rec_map< key >::iterator {
    rec_map_iterator( typename rec_map< key >::iterator i)
    : rec_map< key >::iterator(i) {}
};

template< class key >
struct rec_map : map< key, vector< rec_map_iterator< key > > > {};

int main( int argc, char ** argv ) {
    rec_map< int > my_map;

    my_map[4];
    my_map[6].push_back( my_map.begin() );

    cerr << my_map[6].front()->first << endl;

    return 0;
}
Potatoswatter
+1 for frightening me. I would never use this in production code though, nasmorns solution is much simpler.
drhirsch
mine allows you to write simpler code besides the one-off trickery. Maybe less elegant, but I think it's more in the spirit of C++ ;v) .
Potatoswatter
Why wouldn't `template<class key> struct rec_map_iterator : rec_map<key>::iterator` require a `typename` in front of `rec_map<key>::iterator`? The same goes for the base initialization list. ( FWIW, como agrees with your GCC, but I don't see why it would.)
sbi
Initially I wrote it that way, and GCC complained that it /knew/ that was a typename because all base classes / base initializers are typenames. The strange thing is that member initializers look like base initializers but aren't typenames. Anyway, I'll take its word that it's enforcing the standard.
Potatoswatter
Would you need to be careful about the array/map notation when adding elements like "my_map[1].push_back(my_map.begin())"? The order of evaluation may cause you to add something you didn't expect.
PinkTriangles
@PinkTriangles: Yes, you would. Note, however, that `my_map.begin()` does not necessarily refer to `my_map[1]`. You would have to do this in two steps anyway: 1) create entry: `my_map[1];` 2) insert iterator `my_map[1].push_back(my_map.find(1));`
sbi
@sbi: Since `insert()` only returns the element if it already exists, you can do `my_map[1].push_back( my_map.insert( make_pair( 1, rec_map<int>::mapped_type() ) ).first );`. Using lines still can enhance performance: ` rec_map<iterator> it = my_map.insert( make_pair( 1, rec_map<int>::mapped_type() ) ).first; it->push_back( it );`
Potatoswatter
oops that should be `rec_map<int>::iterator it = …`
Potatoswatter
@Potatoswatter: That's a mean piece of code to put into one line, but I guess you're right and this should work. `:)` I suppose you presume the second to be faster because it eliminates the `my_map[1]` lookup?
sbi
@Potatoswatter: Oh, and BTW, thanks for your explanations regarding `typename`. I would have thought that, if it is required, it is always required. This rule that it is only required where there could be something else than a type seems pretty arbitrary when you see the compiler warn that, what it interprets as an object, might meant to be a type, immediately followed by an error that there cannot be an object at this place...
sbi
+2  A: 

I didn't like deriving from a container in my previous answer so here's an alternative:

template< class key >
struct rec_map_gen {
    struct i;
    typedef map< key, vector< i > > t;
    struct i : t::iterator {
     i( typename t::iterator v )
     : t::iterator(v) {}
    };
};

Now you have to use rec_map_gen<int>::t, rec_map_gen<int>::t::iterator, etc, but you also have access to all std::map's constructors. It's too bad C++ doesn't allow typedefs to be templated.

Using a derived iterator type should be OK. You can still initialize a reverse iterator from an element of this structure, for example.

Potatoswatter