0

I have been trying to implement something that I believe should be pretty straightforward, but am having issues with it. In short, I have a class that has some Q_Properties, some of which I want to be namespace-level enum values. My issue is, if I move the enum and its namespace into a separate file, the QProperty system doesn't recognize the property is an enum value.

Here is a stripped down version to illustrate:

One file contains a namespace with an enum and the class I'm trying to use with the enums:

#ifndef ENUMPROPERTYTEST_H
#define ENUMPROPERTYTEST_H

#include <QObject>
#include "enumTest.h" //contains the other namespace and enum I want to use

namespace Test_SameFile {
Q_NAMESPACE

enum NSEnum_SameFile {
    A1,
    A2,
    A3
};
Q_ENUM_NS(NSEnum_SameFile)

}

class EnumPropertyTest : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int INT1 MEMBER myInt)
    //this enum is declared in the same file and works as expected
    Q_PROPERTY(Test_SameFile::NSEnum_SameFile ENUMVAL1 MEMBER a)
    //this enum is declared in a separate header file and does not
    Q_PROPERTY(Test_DiffFile::NSEnum_DiffFile ENUMVAL2 MEMBER g)

public:
    explicit EnumPropertyTest() : QObject() {}
    ~EnumPropertyTest() {}

private:
    int myInt = 5;
    Test_SameFile::NSEnum_SameFile a = Test_SameFile::A2;
    Test_DiffFile::NSEnum_DiffFile g = Test_DiffFile::T2;
};

#endif // ENUMPROPERTYTEST_H

My header file that contains another namespace and enum:

#ifndef ENUMTEST_H
#define ENUMTEST_H

#include <QObject>

namespace Test_DiffFile {
Q_NAMESPACE

enum NSEnum_DiffFile {
    T1,
    T2,
    T3
};
Q_ENUM_NS(NSEnum_DiffFile)

}

#endif // ENUMTEST_H

My main:

#include <QObject>
#include <QMetaProperty>
#include <QDebug>

#include "enumpropertytest.h"

int main()
{
    auto id1 = qRegisterMetaType<Test_SameFile::NSEnum_SameFile>();
    auto id2 = qRegisterMetaType<Test_DiffFile::NSEnum_DiffFile>();
    auto e1 = QMetaEnum::fromType<Test_SameFile::NSEnum_SameFile>();
    qDebug() << "e1 id: " << id1 << ", valid:" << e1.isValid() << ", scope : " << e1.scope();

    auto e2 = QMetaEnum::fromType<Test_DiffFile::NSEnum_DiffFile>();
    qDebug() << "e2 id: " << id2 << ", valid:" << e2.isValid() << ", scope : " << e2.scope();

    EnumPropertyTest t;

    auto mo = t.metaObject();

    for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i){
        QString propName = mo->property(i).name();
        auto prop = mo->property(i);
        qDebug() << "Property: " << propName << "is an enum: " << prop.isEnumType();
    }

    return 0;
}

When I run this program, it outputs the following:

e1 id:  1024 , valid: true , scope :  Test_SameFile
e2 id:  1025 , valid: true , scope :  Test_DiffFile
Property:  "INT1" is an enum:  false
Property:  "ENUMVAL1" is an enum:  true
Property:  "ENUMVAL2" is an enum:  false

As you can see, the enum declared in the same file as the class is working as expected with the property system, but the one declared in the header file is not recognized to be an enum. How can I resolve this? I have deleted build folders, re-run qmake, everything obvious I could think of and no change.

Cobalt
  • 938
  • 9
  • 21

2 Answers2

0

I believe that in order for your enum to be recognized by Qt's meta type system, it needs to declared within either a Q_OBJECT or a Q_GADGET. If you don't want to make it part of your class (for understandable reasons like reusability, etc), you can declare a separate struct with the Q_GADGET macro in it and then you can define your enum there. The bottom line is that if you want your type to be recognizable by Qt's meta type system, but don't wanna make it a QObject, you can do so by just making it a Q_GADGET. The difference between Q_OBJECT and Q_GADGET is that a gadget cannot have signals and slots.

So, it will look like this:

#include <QObject>

namespace Test_SameFile {    

struct EnumHolder {
private:
  Q_GADGET
public:

enum NSEnum_SameFile {
    A1,
    A2,
    A3
};
Q_ENUM(NSEnum_SameFile)    
}   

}

You can try it and see if it works.

santahopar
  • 2,933
  • 2
  • 29
  • 50
  • If this is the case, why does the `enum` declared in the same file work as expected? It is not in a `Q_OBJECT` or anything either – Cobalt Dec 22 '20 at 00:14
  • @Cobalt, I'm not sure about that. Maybe you can try adding a `Q_DECLARE_META_TYPE` in your `EnumTest.h` file. It might or might not help. I just saw that `Q_NAMESPACE` and `Q_ENUM_NS` are recent additions to `Qt`. Haven't really used them. – santahopar Dec 22 '20 at 00:59
  • according to [qt docs](https://doc.qt.io/qt-5/qobject.html#Q_ENUM_NS) there is no need to use `Q_DECLARE_META_TYPE` with the `Q_ENUM_NS` macro – Cobalt Dec 22 '20 at 01:23
0

I tried to replicate your source code and I got true for all these case. Please take a look:

This is EnumHolder.h and it defines two namespaces YoYo and YaYo containing enums Fruit and Vegetable respectively:

#ifndef ENUMHOLDER_H
#define ENUMHOLDER_H

#include <QMetaProperty>

namespace YoYo {
Q_NAMESPACE

enum Fruit {
  Apple,
  Pear,
  Watermelon,
};
Q_ENUM_NS(Fruit)

}  // namespace YoYo

namespace YaYo {
Q_NAMESPACE
enum Vegetable {
  Broccoli,
  Tomato,
  Lettuce,
};
Q_ENUM_NS(Vegetable)
}  // namespace YaYo

#endif // ENUMHOLDER_H

And this is Food.h, which defines the enum FoodType in namespace Embedded and it also defines a QObject called Food that contains 3 properties of types YoYo::Fruit, YaYo::Vegetable and Embedded::FoodType:

#ifndef FOOD_H
#define FOOD_H

#include <QMetaProperty>
#include <QObject>

#include "EnumHolder.h"

namespace Embedded {
Q_NAMESPACE
enum FoodType {
  Food1,
  Food2,
  Food3,
};
Q_ENUM_NS(FoodType)
}  // namespace Embedded

class Food : public QObject {
  Q_OBJECT
  Q_PROPERTY(YoYo::Fruit fruit MEMBER _fruit)
  Q_PROPERTY(YaYo::Vegetable vegetable MEMBER _vegetable)
  Q_PROPERTY(Embedded::FoodType foodType MEMBER _foodType)
 private:
  YoYo::Fruit _fruit;
  YaYo::Vegetable _vegetable;
  Embedded::FoodType _foodType;
};

#endif  // FOOD_H

And here is my main.cpp:

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);
  Food food;
  auto mo = food.metaObject();

  for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
    auto property = mo->property(i);
    qDebug() << property.name() << " is an enum: " << property.isEnumType();
  }

  return app.exec();
}

And here is my output:

fruit  is an enum:  true
vegetable  is an enum:  true
foodType  is an enum:  true

Also, I did not use qRegisterMetaType() or Q_DECLARE_META_TYPE. It just worked the way it is.

Another note that I'd like to make is that it works as a proof of concept, but it's not a good idea in general cause generally don't wanna define too many namespaces in your code unless there is a need for it. And here it feels like just to have the enum in a separate file you have to define a new namespace with a Q_NAMESPACE macro in it. I tried to reuse the same namespace and define the other enum in a different header file and got a MOC error. You can use Q_NAMESPACE only once apparently for a given namespace. And if you don't have it on top of your enum, you can't use Q_ENUM_NS, cause you'll get another error.

So probably it's a good idea to have one header file where you will define all your enums in a namespace and that way you will use Q_NAMESPACE only once for that namespace and add all those enum's to Qt's meta-object system.

santahopar
  • 2,933
  • 2
  • 29
  • 50
  • interesting. I see you have a `QCoreApplication` in yours. I will recreate your setup and see if I get the same output. – Cobalt Dec 22 '20 at 16:39
  • 1
    I copied your answer and my output says that 'fruit' and 'vegetable' are not enums...how strange. Perhaps the version of Qt I am using has a bug. I am using 5.12.2 for embedded linux, what are you using? – Cobalt Dec 22 '20 at 16:50
  • @Cobalt, 5.14.12 for Windows with MSVC2017 compiler. – santahopar Dec 22 '20 at 18:06