Search code examples
c++qtc++11qt5using-declaration

Q_ENUMS and using declarations do not work together


Consider the following class definition:

// exported.hpp

#include <QObject>

class Exported: public QObject {
    Q_OBJECT

public:
    using QObject::QObject;
    enum class FOO { BAR };
    Q_ENUM(FOO)
};

And the following main file:

// main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include "exported.hpp"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;
    qmlRegisterType<Exported>("Package", 1, 0, "Exported");
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));
    return app.exec();
}

By doing this, I can easily access the named constants of my enum in QML.
As an example:

// main.qml

import QtQuick 2.7
import QtQuick.Controls 2.0
import Package 1.0

ApplicationWindow {
    Rectangle {
        Component.onCompleted: {
            console.log(Exported.BAR)
        }
    }
}

This works as long as the declaration of the enum is enclosed in the class.
As an example, if I change the class definition as shown below, it doesn't work anymore:

// exported.hpp

#include <QObject>

enum class FOO { BAR };

class Exported: public QObject {
    Q_OBJECT

public:
    using QObject::QObject;
    using FOO = ::FOO;
    Q_ENUM(FOO)
};

Now, Exported.BAR in the QML file is undefined.


The most basic question is: why it doesn't work with using declarations?
Note that it works with forwarded declarations, as an example:

// exported.hpp

#include <QObject>

enum class FOO { BAR };

class Exported: public QObject {
    Q_OBJECT

public:
    using QObject::QObject;
    enum class FOO;
    Q_ENUM(FOO)
    enum class FOO { BAR };
};

This is true to the documentation of Q_ENUM actually (emphasis mine):

This macro registers an enum type with the meta-object system. It must be placed after the enum declaration in a class that has the Q_OBJECT or the Q_GADGET macro.

No mentions of a definition in the whole section.
On the other side, we have this from the standard:

A using-declaration introduces a set of declarations into the declarative region in which the using-declaration appears.

So, I was expecting it to work as well. Anyway, this was maybe a wrong expectation on my side.

That said, any suggestion on how to deal with such an inconvenient?
The only way I can see to work around it in case the enum is defined out of the class is to define another enum within the class and have a one-to-one mapping between them.
It's far from being maintainable and a bit tedious indeed.


Solution

  • This has nothing to do with standard C++, at least not directly. Qt's moc is quite limited in its understanding of C++ syntax rules (and with good reason1). Apparently this way of importing enums into class scopes is outside of it's abilities.

    In my code, when I want moc to generate enum <-> string conversions for me, I go with the alias, but in the opposite direction:

    class Exported: public QObject {
        Q_OBJECT
    
    public:
        using QObject::QObject;
    
        enum class FOO { BAR };
    
        Q_ENUM(FOO)
    };
    
    using FOO = Exported::Foo;
    

    This keeps moc happy and is valid C++. The downside is that you pull up the definition of Exported into every scope you use FOO's definition in, and you can't forward-declare FOO.

    1 it was created before libclang was a thing and the implementation cost of a full C++ parser for a few corner cases would be extremely uneconomical.