14

Update

I have created an qt bugticket hoping the documentation will be extended.

Original Question

Believing an Question from 2010 and the Qt Documentation, the operator==() doesn't work with custom types.

Quote:

bool QVariant::operator==(const QVariant & v) const

Compares this QVariant with v and returns true if they are equal; otherwise returns false.

QVariant uses the equality operator of the type() it contains to check for equality. QVariant will try to convert() v if its type is not the same as this variant's type. See canConvert() for a list of possible conversions.

Warning: This function doesn't support custom types registered with qRegisterMetaType().

I've tried to reproduce the repro case from the Stackoverflow Question from 2010 and the comparison worked without any problems for me.

I also went a step further and tried comparisons using an own class which also worked perfectly. To reproduce, put the following code into any header:

enum MyEnum { Foo, Bar };
Q_DECLARE_METATYPE(MyEnum)

class MyClass
{
  int value;
public:
  MyClass() : value(0)
  {
  }

  MyClass(int a) : value(a)
  {
  }

  bool operator==(const MyClass &) const
  {
    Q_ASSERT(false); // This method seems not to be called
    return false;
  }

  bool operator!=(const MyClass &) const
  {
    Q_ASSERT(false); // This method seems not to be called
    return true;
  }
};

Q_DECLARE_METATYPE(MyClass)

And the following code into any function:

QVariant var1 = QVariant::fromValue<MyEnum>(Foo);
QVariant var2 = QVariant::fromValue<MyEnum>(Foo);
Q_ASSERT(var1 == var2); // Succeeds!

var1 = QVariant::fromValue<MyEnum>(Foo);
var2 = QVariant::fromValue<MyEnum>(Bar);
Q_ASSERT(var1 != var2); // Succeeds!

QVariant obj1 = QVariant::fromValue<MyClass>(MyClass(42));
QVariant obj2 = QVariant::fromValue<MyClass>(MyClass(42));
Q_ASSERT(obj1 == obj2); // Succeeds!

obj1 = QVariant::fromValue<MyClass>(MyClass(42));
obj2 = QVariant::fromValue<MyClass>(MyClass(23));
Q_ASSERT(obj1 != obj2); // Succeeds!

I would guess that in newer qt versions the size of a type is aquired when the Q_DECLARE_METATYPE is used so the QVariant can compare values of unknown types bytewise.

But that's only a guess and I don't want to risk the stability of my application by guessing what qt does instead of relying on the documentation.

Can I find out, how the QVariant compares unknown types? I would prefer relying on specification than on implementation.

Community
  • 1
  • 1
Random Citizen
  • 1,272
  • 3
  • 14
  • 28
  • did you call `qRegisterMetaType()` on `MyEnum` and `MyClass`? – Zaiborg Oct 31 '13 at 10:27
  • No, I did not call `qRegisterMetaType()`. There are no code lines using MyClass or MyEnum except the lines I've posted above. – Random Citizen Oct 31 '13 at 11:14
  • have you tried to cast the values back to its original type bevore comparing? (`QVariant::value()`) also what is for example the type of `var1`? (or to be clear, what does the variable say what type it is) – Zaiborg Oct 31 '13 at 11:56
  • `have you tried to cast the values back to its original type bevore comparing? (QVariant::value())` That's actually what I am trying to avoid. I am programming a property widget and I want to be able to compare properties between multiple objects to know which widgets to enable, whether multiple selected objects have the same value... So using an abstract way to compare values is very desirable for my use-case. – Random Citizen Oct 31 '13 at 15:23
  • you wana compare two values, and your operators didnt get called ... so for some reason `QVariant` does the right thing, you can try to find out wat is going on or live with the black magic behind the scenes – Zaiborg Nov 02 '13 at 09:35

1 Answers1

27

I'm afraid you'll need to rely on the code (and, being behaviour, it can't be changed without breaking), and not documentation. There's a surprise just below, though.

Here's the relevant code.

QVariant::operator== for types with unregistered operators will just use memcmp. The relevant snippet (in 5.1) is this:

bool QVariant::cmp(const QVariant &v) const
{
    QVariant v1 = *this;
    QVariant v2 = v;
    if (d.type != v2.d.type) 
        // handle conversions....

    return handlerManager[v1.d.type]->compare(&v1.d, &v2.d);
}

handlerManager is a global object that gets used to perform type-aware manipulations. It contains an array of QVariant::Handler objects; each of such objects contains pointers to perform certain operations on the types they know how to handle:

struct Handler {
    f_construct construct;
    f_clear clear;
    f_null isNull;
    f_load load;
    f_save save;
    f_compare compare;
    f_convert convert;
    f_canConvert canConvert;
    f_debugStream debugStream;
};

Each and every of those members is actually a pointer to a function.

The reason for having this array of global objects is a bit complicated -- it's for allowing other Qt libraries (say, QtGui) to install custom handlers for the types defined in those libs (f.i. QColor).

The operator[] on the handlerManager will perform some extra magic, namely get the right per-module handler given the type:

return Handlers[QModulesPrivate::moduleForType(typeId)];

Now the type is of course a custom type, so the Handler returned here is the one the Unknown module. That Handler will use the customCompare function in qvariant.cpp, which does this:

static bool customCompare(const QVariant::Private *a, const QVariant::Private *b)
{
    const char *const typeName = QMetaType::typeName(a->type);
    if (Q_UNLIKELY(!typeName) && Q_LIKELY(!QMetaType::isRegistered(a->type)))
        qFatal("QVariant::compare: type %d unknown to QVariant.", a->type);

    const void *a_ptr = a->is_shared ? a->data.shared->ptr : &(a->data.ptr);
    const void *b_ptr = b->is_shared ? b->data.shared->ptr : &(b->data.ptr);

    uint typeNameLen = qstrlen(typeName);
    if (typeNameLen > 0 && typeName[typeNameLen - 1] == '*')
        return *static_cast<void *const *>(a_ptr) == *static_cast<void *const *>(b_ptr);

    if (a->is_null && b->is_null)
        return true;

    return !memcmp(a_ptr, b_ptr, QMetaType::sizeOf(a->type));
}

Which, apart from a bit of error checking and handling shared and null variants in a special way, uses memcmp on the contents.

... only if the type is not a pointer type, it seems. Wonder why there's that code there...


Good news!

Starting with Qt 5.2, you can use QMetaType::registerComparator (see here) to make Qt invoke operator< and operator== on your custom type. Just add to your main:

qRegisterMetaType<MyClass>();
QMetaType::registerComparators<MyClass>();

And voilà, you'll hit the assert in your equality operator. QVariant::cmp now is:

QVariant v1 = *this;
QVariant v2 = v;
if (d.type != v2.d.type) 
    // handle conversions, like before

// *NEW IMPORTANT CODE*
if (v1.d.type >= QMetaType::User) {
    // non-builtin types (MyClass, MyEnum...)
    int result;
    // will invoke the comparator for v1's type, if ever registered
    if (QMetaType::compare(QT_PREPEND_NAMESPACE(constData(v1.d)), QT_PREPEND_NAMESPACE(constData(v2.d)), v1.d.type, &result))
        return result == 0;
}
// as before
return handlerManager[v1.d.type]->compare(&v1.d, &v2.d);
peppe
  • 21,934
  • 4
  • 55
  • 70
  • Amazing!! I think if Qt were born today it would use a more extensive use of templates. But compiler's standard compliance was not so widespread as today (namely, the signal/slots mechanism). – ABu Apr 01 '17 at 21:52