PDA

View Full Version : How can QMultiMap::contains() differ from QMultiMap::keys().contains()?



donalbane
26th August 2016, 21:42
I have the following code:



QList<QPersistentModelIndex> keys = indexToPlacemarkMap.keys();
bool listLookup = keys.contains(QPersistentModelIndex(index));
bool multimapLookup = indexToPlacemarkMap.contains(QPersistentModelIndex (index));


where index is a QModelIndex and indexToPlacemarkMap is a QMultiMap<QPersistentModelIndex,GeoDataPlacemark*>.

In some cases listLookup = true and multimapLookup = false. I don't understand how this is possible. The documentation for QMultiMap::contains indicates that it searches the keys, so I would expect these two bools to always be equal. Can anyone explain to me why they might not be equal? QPersistentModelIndex(index) is checked for validity and it is valid.

Don

sedi
27th August 2016, 00:16
Not sure, but I'll try:
QMultiMap - as opposed to QMap - allows more than one value per key. If you ask for the keys(), you will not be given doublettes, right? But those doublettes will probably exist internally - each one with its own index.

So if listLookup = true and multimapLookup = false, you simply have an index to a key, that is not part of .keys() method's output list, but rather pointing to a doublette with some other value.

d_stranz
27th August 2016, 01:50
Not sure, but I'll try

I don't think that's right. The code posted deals only with the keys, not the values stored by them.

Lines 2 and 3 each construct a temporary QPersistentModelIndex; the call to indexToPlacemarkMap.keys() in line 1 also creates a temporary QList<> of QPersistentModelIndex instances, using the QPersistentModelIndex copy constructor.

So you have potentially three different instances of QPersistentModelIndex you are trying to compare. I don't know if it is necessarily true that two QPersistentModelIndex instances that point to the same QModelIndex will compare as equal using the version of QPersistentModelIndex::operator==() that compares two QPersistentModelIndex instances.

That is:



QModelIndex index;
QPersistentModelIndex a( index );
QPersistentModelIndex b( index );

assert( a == index ); // Should be true
assert( b == index ); // Should also be true
assert( a == b ); // But is this true?


In lines 2 and 3 of your code, try replacing "QPersistentModelIndex( index )" with just "index"; then you will be invoking the version of QPersistentModelIndex::operator==() that compares the two wrapped QModelIndex instances for equality.

donalbane
29th August 2016, 18:41
In lines 2 and 3 of your code, try replacing "QPersistentModelIndex( index )" with just "index"; then you will be invoking the version of QPersistentModelIndex::operator==() that compares the two wrapped QModelIndex instances for equality.

Thanks, d_stranz. That is actually how the code was originally written, and it exhibits this behavior as well. I had added the casting to QPersistentModelIndex when debugging the behavior to see if that was the issue, but it was not.

My original debug code looked something like this:



foreach(QPersistentModelIndex map_pindex, indexToPlacemarkMap.keys() )
{
QModelIndex debugIndex = QModelIndex(map_pindex);
if(debugIndex == index)
{
bool itsInThere = true;
}
if(!indexToPlacemarkMap.contains(map_pindex))
{
bool lookupFailed = true;
}
}


where I was seeing cases where itsInThere was true but lookupFailed was also true. The fact that I could iterate over the keys but have the contains method fail was what led me to see that there seemed to be a difference in the contains method behavior on the keys QList vs QMultiMap.

d_stranz
29th August 2016, 23:37
I had added the casting

Well, in this case, it isn't actually casting, it's calling the QPersistentModelIndex constructor that takes a QModelIndex as argument. That probably has some subtly different behavior than a pure type cast between two first-class types (eg. int to double).


QModelIndex debugIndex = QModelIndex(map_pindex);

I am surprised this compiles. There is no constructor for QModelIndex that takes a QPersistentModelIndex as argument, and the compiler won't let you cast between dissimilar types.

anda_skoa
30th August 2016, 09:14
I am surprised this compiles. There is no constructor for QModelIndex that takes a QPersistentModelIndex as argument, and the compiler won't let you cast between dissimilar types.

QPersistentModelIndex has a cast operator to QModelIndex, so the compiler chose that to convert map_pindex into a QModelIndex and passed that to the QModelIndex copy constructor.
http://doc.qt.io/qt-5/qpersistentmodelindex.html#operator-const-QModelIndex--and

@donalbane: have you tried how QMultiMap behaves with a simple key type, e.g. int?

Cheers,
_

d_stranz
30th August 2016, 15:29
QPersistentModelIndex has a cast operator to QModelIndex

I looked and look for that and somehow didn't see it in the list of methods. I knew there had to be something like that.

donalbane
30th August 2016, 23:29
@donalbane: have you tried how QMultiMap behaves with a simple key type, e.g. int?

@anda_skoa: Most of the time QMultiMap works as expected. We are just seeing a particular state encountered with QPersistentModelIndex keys that exhibits the strange behavior. I wasn't sure if it was a bug or if what I was seeing was normal and I just did not understand the expected behavior. Were I to construct a sample test using int keys, or even QPersistentModelIndex keys, I'm sure that it would work as expected. But, I still don't understand how what I am seeing is possible.

anda_skoa
31st August 2016, 09:44
One cause would be if the model had changed while the map is filled.

I.e. if the model changes, then the persistent model indexes get updated.
Which could lead to new value regarding the sorting criteria of the map, which would make the map inconsistent.

Extracting all keys doesn't need the sorting to be intact, but a contains lookup does.

I.e. you can still iterate over the data even if it is not a map anymore, but using map functionality is only possible as long as the map's invariant is still valid.

Cheers,
_