Wednesday, July 28, 2010

QMetaEnum Magic - Serializing C++ enum's

Qt has a number of useful classes and utilities; among these is QMetaEnum which provides the ability to serialize and deserialize C++ enumerations through the use of moc, the meta-object compiler.

Method 1 - Enum's within a QObject (fairly common)

First, let's take a look at a common example of an enum within a class as provided in the "Secrets of Qt Full" developer days presentation:

class Person : public QObject {
    Q_OBJECT
    enum  Qualification { Student, ... };
    Q_ENUMS(Qualification)
};

When moc runs on the above and sees Q_OBJECT it adds a staticMetaObject member (that's static, big surprise) of type QMetaObject. This QMetaObject instance has an indexOfEnumerator and an enumerator member functions that make it possible to access the QMetaEnum representing the Person::Qualification enum.

The code to access the QMetaEnum member looks something like the following:

const QMetaObject &mo = Person::staticMetaObject;
int index = mo.indexOfEnumerator("Qualification"); // watch out during refactorings
QMetaEnum metaEnum = mo.enumerator(index);

We can then use the QMetaEnum object as follows:

// first, let's convert from an enum value to a string
Qualification q = Person::Student;
QByteArray str = metaEnum.valueToKey(q);
// str now contains "Student"

// second, let's convert from a string to an enum value:
int value = metaEnum.keyToValue("Student");
Qualification q = static_cast(value);

Method 2 - Without a QObject-based class (less common)

UPDATE: Based on Vladislaw's comment, I've done further research and added an additional post which demonstrates how to accomplish this in namespaces other than Qt.

Another less well-known feature is that with only minor effort you can use this same feature without needing a QObject-based class as a container. For this example, I'll use the Qt::Key enum. Since we don't want a QObject based class, rather than using Q_OBJECT we'll use the Q_GADGET macro within a container:

class Container {
 Q_GADGET
 Q_PROPERTY(Qt::Key key_enum);
public:
 Qt::Key key_enum;
};

Like Q_OBJECT, Q_GADGET creates a staticMetaObject member that we'll need to use to access the QMetaEnum:

const QMetaObject &mo = Container::staticMetaObject;
int prop_index = mo.indexOfProperty("key_enum");
QMetaProperty metaProperty = mo.property(prop_index);
QMetaEnum metaEnum = metaProperty.enumerator();

Unlike the first example, in this example I referenced the Qt::Key typed key_enum property to get a hold on the QMetaEnum object that I can now use exactly as before:

// convert to a string
Qt::Key key = Qt::Key_Down;
QByteArray str = metaEnum.valueToKey(key);
qDebug() << "Value as str:" << str;

// convert from a string
int value = metaEnum.keyToValue("Key_Up");
key = static_cast(value);
qDebug() << "key is Key_Up: " << (key == Qt::Key_Up);

Which results in the following output:

Value as str: "Key_Down" 
key is Key_Up:  true

And there you have it... an easy way to leverage Qt to serialize C++ enums.