Detecting if 2 Iterators point to the same thing?

Started by
39 comments, last by Sean_Seanston 9 years ago

Basically, I have a situation where I have 2 containers of the same type, and I want to iterate through them and know when I've reached an element that was previously selected by being pointed at by an iterator.

I tried simply using == but that caused a crash. It's also possible of course that I did it wrong.

How is this meant to be done? Or should it be avoided if possible?

Advertisement
It would be very helpful if you could post a small program that shows what you tried.

iterators from two different containers will never be equal. If you dereference them you can compare the actual values of the objects in the two containers. Maybe that's what you want? Don't dereference an iterator equal to yourContainer.end() though.


If you dereference them you can compare the actual values of the objects in the two containers. Maybe that's what you want? Don't dereference an iterator equal to yourContainer.end() though.

I thought along those lines... but then a problem I envisioned is that it's possible for the objects to be perfectly equal in value, therefore you'd need to know if they were different actual instances of such objects, and then we're probably back at square one...

Basically, it's for a system for moving items between 2 inventories. So Inventory A might have 200 of Item X and that might be selected, but Inventory B could also have 200 of Item X... so it's very important that it refers to the exact instances involved.

It would be very helpful if you could post a small program that shows what you tried.

I'll try to cut out the relevant bits that hopefully show enough to get the idea across without getting into anything irrelevant...

- I have an Inventory class which represents the inventory of, for example, a player.

This contains a map consisting of a pair representing the item name and its condition as an int, and another int as the value of the map which represents the quantity:


std::map< std::pair< std::string, int >, int > itemStock;

- This stock container can be accessed like so:


const std::map< std::pair< std::string, int >, int >& getStock();

- I have an iterator to remember the currently selected item for the purposes of a user interface:


std::map< std::pair< std::string, int >, int >::const_iterator selInvItem;

- What I tried then as I iterated through an inventory's stock container, was the following:


for( std::map< std::pair< std::string, int >, int >::const_iterator it = stock.begin(); it != stock.end(); it++ )
{
         if( selInvItem == it )
         {
                  //Found the currently selected item
         }
}

But it crashes with "Expression: map/set iterators incompatible".

Have I missed something?

I stepped through it withe breakpoints and the iterator I was using (selInvItem) didn't seem to be getting initialized for some reason... at least it was showing up as 0... but either way I have it so "it" is being compared with another iterator that shows up like it should when I step through the program and it still gives the same error.

Is comparing iterators that point to different containers just not a thing you do?

If you want to know if two iterators point to the same thing, you need to compare the thing they point to, not the iterators themselves.

Comparing iterators from two different containers is undefined behavior.


Is comparing iterators that point to different containers just not a thing you do?

It is not a thing you do (like I said before).


but then a problem I envisioned is that it's possible for the objects to be perfectly equal in value, therefore you'd need to know if they were different actual instances of such objects, and then we're probably back at square one...

If comparing objects by value is not acceptable, and you're storing objects by value (which you are) in two separate containers, then what you're asking is impossible. By definition the "same" object will be two different instances - they can't be the same, they occupy different spaces in memory. One is a copy of the other.

You either need to be comparing pointers, or give the objects some unique id and compare by value using that unique id (so two copies of the same object will compare as equal).

And taking a pointer to one of the objects in your containers limits your options. If you add or remove anything to the container, the pointer (and similarly any iterator from that container) may now be invalid.

I assuming the data structures you're using right now is because you want to treat items of a certain type and of a certain condition as the same right? Like, if you buy 4 brand new bolts of cloth, you want it to be merged with the player's existing x bolts of brand new cloth? The data structures you have now don't allow for multiple instances of an item of a particular condition in one container.

As mentioned, you can't compare two iterators from different containers. You can however, compare the elements/objects the iterators are referring to.

If you want to know if the two elements are "equal" then do:


*itA == *itB

...which dereferences the iterators (similar to pointers, except using the iterators' custom * and -> operator overloads), and compares them using that type's equality operator (==), which may have been overloaded by that type.

If you want to know whether they are the same object in memory, and not two separate but equal objects, you can compare their addresses in memory (the addresses of the objects, not the addresses of the iterators).

This could be done like this:


&(*itA) == &(*itB)

Dereferencing the iterators to get (references to) the actual elements, and then getting the addresses of those actual elements in memory, and comparing those addresses.

However, you have to be aware of what data you are comparing - when using multiple inheritance, if a class inherits from both BaseA and BaseB, and you are accessing it through a BaseA* and a BaseB* pointer, the two pointers (pointing at the same class instance in memory) will have different addresses - each inherited class potentially being offset from the final derived instance.

Depending on what you are doing, you might just want to give each instance a unique ID, and then compare the IDs.


This could be done like this:
&(*itA) == &(*itB)
Dereferencing the iterators to get (references to) the actual elements, and then getting the addresses of those actual elements in memory, and comparing those addresses.

If those two iterators are from different containers, how could that ever evaluate to true? Unless you can have containers somehow overlapping in memory...


If those two iterators are from different containers, how could that ever evaluate to true? Unless you can have containers somehow overlapping in memory...

One of the nifty things about iterators is that they were designed around the concept of pointers. Pointers are valid iterators.

And one of the nifty things about containers is that while they can implement ownership semantics, they are not forced to implement ownership semantics. An array is a perfectly valid container.

It is perfectly legal to create a container class that operates on existing allocated memory. It is also perfectly legal to have any number of iterators -- including raw pointers -- that refer to the same objects on resolution.

So while the example Servant gives will rarely be used, it is certainly valid.

The language standard leaves as undefined what happens if you compare the iterators themselves (it may crash, it may format your hard drive, it may happen to do what you wanted), but it is entirely possible that the objects the iterators are referencing can be identical objects.

I would think the standard also makes Servants solution undefined behaviour, as its comparing 2 pointers to possibly disjoint memory regions, although it often would succeed.


reinterpret_cast<uintptr_t>(&(*itA)) == reinterpret_cast<uintptr_t>(&(*itB))

That could reduce the theoretical possibility of a crash, but might still allow for an undefined result, therefore I think you should avoid doing any of this.

If you are not satisfied with the result of *itA == *itB you should just override the operator== for the data objects class.

This topic is closed to new replies.

Advertisement