10

For the new C++17 std::unordered_map::extract function the documentation says:

Extracting a node invalidates only the iterators to the extracted element, and preserves the relative order of the elements that are not erased. Pointers and references to the extracted element remain valid, but cannot be used while element is owned by a node handle: they become usable if the element is inserted into a container.

Naturally, extract invalidates iterator of the extracted (which is a thing of the container, from which the element was removed). But the documentation is funky about references and pointers - it says these remain valid but cannot be used until re-inserted into (possibly another) container - case in which they will have retained their values (?).

Question: My use case is to examine an element after extracting, i.e. do an erase-examine-discardForGood operation with only one hash lookup. The extract function seemed perfectly suited for this however documentation suggests I can't use node_type to examine the element. Is my understanding correct?

Barry
  • 286,269
  • 29
  • 621
  • 977
haelix
  • 4,245
  • 4
  • 34
  • 56

2 Answers2

7

You can think that extract (and corresponding insert) "magically" changes the type of the affected map element: when the element is owned by the map, it has type std::pair<const key_type, mapped_type>, but when the element is owned by the node handle, it has type std::pair<key_type, mapped_type> (so you can change the value of the key).

Thus, if you acquire a reference/pointer to an element when the element is owned by the map, then you can't use that reference/pointer after the element is extracted and before it is re-inserted, or you will violate the strict aliasing rule.

However, it is totally fine to use a reference/pointer acquired after the extraction.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • That's good insight, thanks... Does you remark apply if holding pointers to directly `value_type`? (not the `pair<>`). – haelix Oct 10 '18 at 14:29
6

Yes, that is what the text says.

At first glance that seems like a fairly arbitrary limitation, though I'm sure there's some good, if arcane, reason for it.

That being said, the handle itself has member functions value()/key()/mapped() that may be of value (!) to you.

Node handle is a move-only type that owns and provides access to the element (the value_type) stored in the node, and provides non-const access to the key part of the element (the key_type) and the mapped part of the element (the mapped_type). (ref)

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • _the handle itself has member functions_ Yes, I settled on using `node_type::value()` but I got confused by the doc. I *am* holding pointers to `value_type`s from the map, which supposedly I should not dereference while the node is detached. Now, such a pointer from when node was still attached, is either equal to `&nodeHandle.value()`, or it's not... if you see my point. – haelix Oct 10 '18 at 14:23
  • to cut the story short - I need to clear some pointers to `map::value_type` from other data structures when a map element is removed. I think it's safe to do so by `extract`-ing that node, and looking for `&nodeHandle.value()` in those structures. – haelix Oct 10 '18 at 14:27
  • @haelix Oh, ouch, well that should work as long as "usable" means what I think it means – Lightness Races in Orbit Oct 10 '18 at 15:32
  • You mean, you're not convinced `extract` will not alter the memory location of `value()` (despite the purpose of the method being, to move/splice non-copyable non-movable objects between containers) -- hmm – haelix Oct 10 '18 at 17:48
  • 3
    @haelix There's some hanky-panky going on under the hood to turn a `pair` into a `pair` (with no moving or copying). It is up to the implementation to make sure it works, but that magic isn't guaranteed to be portable. The language lawyers get rather excitable when type-punning comes up. There's compiler optimizations to worry about, cache invalidations to worry about, and I can't remember what all else. As long as you're comparing pointers, and not dereferencing your cached pointers, I think you're well within bounds. – Howard Hinnant Oct 10 '18 at 23:00
  • Ok that's very helpful. – haelix Oct 10 '18 at 23:09
  • 1
    Yep, that. The actual location is very unlikely to change (why would it change back?) but there are complexities involved that come from C++ not being a baremetal programming language but instead an abstraction with a complex type system and blah blah blah. One could argue that this in itself is a huge abstraction leak, but that's what happens when would-be language features are implemented in "userspace" by a library. At least we get the feature I guess. – Lightness Races in Orbit Oct 11 '18 at 09:37