Coding tip; pretty-printing enum values.

Posted by Thomas Zander on October 9, 2008 · 16 comments

I’ve been in computers too long, whenever someone talks to me about error codes I think about the very old MacOs releases, or even longer ago the Amiga crash codes.
But I’m afraid you see them still, a dialog giving a very helpful “sorry, error 1234 happened”.

Now, anyone that is a coder knows how difficult it is to handle each and every error with a properly written error message. Preferrably one that actually makes sense to the user. And realistically speaking this tends not to happen. But before now the effect was typically to have useless messages like the above, or use some hacks that might get out of date on refactorings.
If you look at Qt there are several classes that have an error state; QNetworkReply is one of them. This error state is an enum and in C++ enums really are just integers.
In Qt we allow an enum to be more than just a set of integers using the Q_ENUMS macro. The direct benefit for you is that you can use introspection (/reflection) to get the string value of an enum.

So, no more excuses for just printing an integer error message, you can at least print the more readable enum value. Which means that technical people that don’t have the API docs for Qt handy can still figure out what went wrong.

Here is the code to do this;

QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError) {
    QString errorValue;
    QMetaObject meta = QNetworkReply::staticMetaObject;
    for (int i=0; i < meta.enumeratorCount(); ++i) {
        QMetaEnum m = meta.enumerator(i);
        if (m.name() == QLatin1String("NetworkError")) {
            errorValue = QLatin1String(m.valueToKey(error));
            break;
        }
    }
    QMessageBox box(QMessageBox::Information, "Failed to fetch",
                "Fetching stuff failed with error '%1`").arg(errorValue),
                QMessageBox::Ok);
    box.exec();
    return 1;
}

In Qt every class that has the Q_OBJECT macro will automatically have a static member "staticMetaObject" of the type QMetaObject. You can then find all sorts of cool things like the properties, signals, slots and indeed enums.

Happy hacking :)

QShare(this)

No related posts.


16 comments

1 Max Howell October 9, 2008 at 3:06 pm
 

Looks like a templated general purpose function is possible to me.

qErrorMessage( code );

Would be splendid.

2 Max Howell October 9, 2008 at 3:08 pm
 

Your blog fails to escape < and >.

3 Marc October 9, 2008 at 4:43 pm
 

Nice. Now, if you used exceptions… :)

4 Andre October 9, 2008 at 6:17 pm
 

This might still get out of date on refactorings, e.g. if the metadata isnt called “NetworkError” anymore. Perhaps it could be more general by giving objects an error state in which case an error message meta data are attached.

5 Matthias Ettrich October 9, 2008 at 7:36 pm
 

Small improvement suggestion: http://doc.trolltech.com/4.4/qmetaobject.html#indexOfEnumerator
:-)

6 mahoutsukai October 9, 2008 at 8:54 pm
 

“In Qt every class that has the Q_OBJECT macro will automatically have a static member “staticMetaObject” of the type QMetaObject.”

More precisely, every _derived_ class that has the Q_OBJECT macro (plus the classes Qt and QObject) will have staticMetaObject. moc aborts with an error if a base class has the Q_OBJECT macro. But for this case one can use the still undocumented (even though something else was promised at last year’s Akademy *sigh*) Q_GADGET macro. Unfortunately, there is a bug in moc which makes it impossible to have a base class with Q_GADGET macro but without enum and a derived class with Q_GADGET macro but with enum because moc will not generate the staticMetaObject for the base class which will cause a linker error (missing symbol BaseClass::staticMetaObject). (Yes, I have submitted a bug report for this a long time ago.)

The rather trivial patch to fix this bug in moc is:

Index: src/tools/moc/moc.cpp
===================================================================
— src/tools/moc/moc.cpp (revision 869687)
+++ src/tools/moc/moc.cpp (working copy)
@@ -683,7 +683,7 @@

next(RBRACE);

- if (!def.hasQObject && def.signalList.isEmpty() && def.slotList.isEmpty()
+ if (!def.hasQObject && !def.hasQGadget && def.signalList.isEmpty() && def.slotList.isEmpty()
&& def.propertyList.isEmpty() && def.enumDeclarations.isEmpty())
continue; // no meta object code required

Maybe it will be applied now. ;-)

7 thomas October 9, 2008 at 10:06 pm
 

Mmh, I’d like to use something similar to sender() in my slotLogMessage(const QString &message) to prefix each message with its source. Is it somehow possible to get a string of the ORIGIN of a signal that was passed thorugh multiple classes? Like, the name of the class that first called emit?

8 Cornelius Schumacher October 9, 2008 at 10:08 pm
 

Of course a simple pragmatic solution to the problem would be to return the error codes as a string instead of an integer in the first place.

9 argonel October 10, 2008 at 3:02 am
 

too bad this can’t be done for QEvent::Type.

10 Kevin Kofler October 10, 2008 at 6:46 am
 

This simple macro should rid you of the copy&paste:
#define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v)))

Use as follows:
QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError) {
QString errorValue = QLatin1String(ENUM_VALUE(QNetworkReply, NetworkError, error));
QMessageBox box(QMessageBox::Information, “Failed to fetch”,
“Fetching stuff failed with error ‘%1`”).arg(errorValue),
QMessageBox::Ok);
box.exec();
return 1;
}

11 Kevin Kofler October 10, 2008 at 6:48 am
 

Oops, of course I mean QLatin1String(ENUM_NAME(QNetworkReply, NetworkError, error)); there. And it ate my indentation. :-(

12 Ralf October 10, 2008 at 9:42 am
 

This was part of Simons presentation on “Secrets Of Qt” during the 2007 developer days. You can find the transcript here: http://labs.trolltech.com/page/Projects/DevDays/DevDays2007

\Ralf

13 Michael "SPEED!" Howell October 10, 2008 at 5:07 pm
 

@Cornelius: I can think of a few problems with that.
1) It would cause the string to be copied whenever the value is transfered between pieces of code. It’s faster to copy an integer.
2) It’s faster to compare an integer with an integer than a string with a string. Useful in error-handling code.
3) Enumerators get their own type, so you get more compile-time checking.

14 Adam Higerd October 10, 2008 at 7:06 pm
 

@Cornelius, Michael: Don’t forget the fact that human-visible strings could be localized!

15 Felix October 10, 2008 at 11:54 pm
 

This won’t help with displaying localized messages to the user, unless enum strings are marked for translation.

16 Adam Higerd October 13, 2008 at 5:20 pm
 

@Felix, et al: Marking the enum strings for translation is actually a really good idea; even if the enum strings themselves are in English you can use Linguist to fill in “localized” English “translations” of those strings for display purposes. (Similarly you can use Linguist to intelligently deal with plurals in the code’s “native” language. It’s a nice trick.)

Comments on this entry are closed.

Previous post:

Next post: