10

My goal is to map elements of a type to other elements of the same type. Suppose they are size_t for simplicity.

std::map<size_t, size_t> myMapping;

This would do it, but if I want to follow a bunch of such links (they are all the same map), each step is a log(n) lookup.

size_t k = /*whatever*/;
myMapping[myMapping[myMapping[k]]];   //3 * log(n)

I want to make use of the fact that map iterators remain valid and have a map that maps size_t to iterators into itself.

typedef /*myMapTemplate*/::iterator map_iter;
std::map<size_t, map_iter> myMapping;

size_t k = /*whatever*/
map_iter entryPoint = myMapping.find(k);
entryPoint->second->second->first;   //log(n) + 2 constant time operations

How would I write this type? I know copying would keep iterators to old map and plan to take care of this myself.

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
Filipp
  • 1,843
  • 12
  • 26
  • 5
    lolwut http://cdn.overclock.net/d/d9/d944c3d8_xzibit-happy.jpeg – Luchian Grigore Oct 10 '12 at 10:35
  • There is an interesting question whether such type can even legally be written – jpalecek Oct 10 '12 at 10:44
  • @jpalacek: I'm pretty sure it can't, but I don't remember the formal proof. Syntactically, you have to name the value type before the iterator type exists, which is impossible since there's no way to forward declare it. Conceptually, a "circular type reference" like this isn't something that C++ compilers are expected to be able to resolve -- C++ type inference isn't that demanding. – Steve Jessop Oct 10 '12 at 10:44
  • This i need to think about, you maybe could use some sort "proxy object" as mapped type that has the same size as an iterator and that has operators that makes it behaves as an iterator. – Jim Hansson Oct 10 '12 at 10:47
  • Why would you want such a thing? – Mark Ingram Oct 10 '12 at 10:48
  • 1
    @MarkIngram different cost in time: log(n) + 2*c VS 3*log(n) – Jim Hansson Oct 10 '12 at 10:53
  • 2
    @SteveJessop: I thought about making it [like this](http://ideone.com/2xFb2). – jpalecek Oct 10 '12 at 11:35
  • @JimHansson why not use `std::unordered_map`? – Mark Ingram Oct 10 '12 at 11:41
  • @jpalecek: nice. Looks like it could be close enough for all practical purposes. – Steve Jessop Oct 10 '12 at 11:46
  • 2
    Hmm, I'm not sure the code you linked to is valid. According to [this answer](http://stackoverflow.com/a/6517325/20984) and its comments, I would say it invokes undefined behavior: using a template specialization as a base class triggers its instantiation, meaning that your code actually instantiates `std::map` while `A` is still incomplete, which is UB. – Luc Touraille Oct 10 '12 at 12:53
  • @LucTouraille: Yeah I'm not sure either. Certainly I would recommend to use CRTP with your own types only (or the ones specifically designed for it). – jpalecek Oct 10 '12 at 14:46
  • @MarkIngram: `std::unordered_map` might drop the lookup time down from O(log n) to O(1), but (a) that's only an average-case complexity and (b) it's still gonna be much slower (higher constant factor) than simply using an iterator. – j_random_hacker Oct 11 '12 at 12:01

2 Answers2

6

I understand your question that you want map: key->map<key,>::iterator

So, here it is, a struct with map iterator as value:

template <
    template <class K, class V, class C, class A> class mapImpl, 
   class K, 
   class V, 
   class C=std::less<K>, 
   class A=std::allocator<std::pair<const K, V> >
>
class value_with_iterator {
public:
   typedef typename mapImpl<const K,value_with_iterator,C,A>::iterator value_type;
   value_type value;
};

Map defined with using struct above:

typedef std::map<size_t, value_with_iterator <std::map, size_t, size_t> > map_size_t_to_itself;

Some insert method - to link key with itself:

map_size_t_to_itself::iterator insert(map_size_t_to_itself& mapRef, size_t value)
{
   map_size_t_to_itself::value_type v(value, map_size_t_to_itself::mapped_type());
   std::pair<map_size_t_to_itself::iterator, bool> res = mapRef.insert(v);
   if (res.second) 
     res.first->second.value = res.first;
   return res.first;
}

And simple test:

int main() {
   map_size_t_to_itself mapObj;
   map_size_t_to_itself::iterator i1 = insert(mapObj, 1);
   map_size_t_to_itself::iterator i2 = insert(mapObj, 1);
   map_size_t_to_itself::iterator i3 = insert(mapObj, 2);

   std::cout << i1->first << ": " << i1->second.value->first << std::endl;
   std::cout << i2->first << ": " << i2->second.value->first << std::endl;
   std::cout << i3->first << ": " << i3->second.value->first << std::endl;
}

with OUTPUT:

1: 1
1: 1
2: 2

Full link: http://ideone.com/gnEhw

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • I see what you did there. Instead of directly mapping size_t to iterators of the same map, you map size_t to structs which contain iterators into maps of those same structs. A bit cumbersome syntax, but it does accomplish exactly what I asked. – Filipp Oct 26 '12 at 02:15
  • You don't need to specify identifiers in template template parameter lists, and it is moderately confusing to re-use the same identifiers in the surrounding parameter list, as they are not related. – Caleth Jan 19 '18 at 14:02
  • You also have the wrong default allocator type, it should be `std::allocator >`, and you should remove `V` from the parameter list, because it isn't the `value_type` of the map – Caleth Jan 19 '18 at 14:03
2

If I understood your problem correctly, I think I would keep my elements in a vector and use a vector of indices into the first vector for the kind of indirection you want. If you also need ordered access you can always throw in a map to the elements of the first vector.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • 3
    This is not a question of "Can I do something in a fast way" but a question of "Can I make the type system do this thing which should be possible but I can't express" Eventually, I would store a vector of such iterators and have a single map represent an entire graph. – Filipp Oct 10 '12 at 11:00
  • @Filipp: "should be possible" in that one can imagine a fixed point of the template meta-function that maps the type `T` to the type `map::iterator`. Not possible in that C++ compilers can't actually construct that fixed point. – Steve Jessop Oct 10 '12 at 11:10
  • As it is the OP needs to "erase" the std::map from the iterator type. It is not too hard to write a polymorphic iterator if he can take the extra indirection. Assuming Boost doesn't already have one, that is. – Nicola Musatti Oct 10 '12 at 11:15