QVariant comparison with own types working?

c++, qt, qt5, qvariant

Solution

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);

Problem

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.

Original source

Related problems