Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QVariant comparison with own types working?

Tags:

c++

qt

qt5

qvariant

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.

like image 362
Random Citizen Avatar asked Oct 31 '13 10:10

Random Citizen


1 Answers

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);
like image 114
peppe Avatar answered Oct 29 '22 09:10

peppe