C++ reflection (P2996) and moc: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 22: Line 22:
* P3491 "define_static_{string,object,array}" https://wg21.link/P3491
* P3491 "define_static_{string,object,array}" https://wg21.link/P3491


== Triggers for moc ==
== What needs to be extracted? ==
moc extracts:
 
* the class name
* the parent class name (following QObject's inheritance chain)
* properties (Q_PROPERTY)
* invokables (methods marked with Q_INVOKABLE, Q_SIGNAL, Q_SLOT; may include constructors)
** for each invokable, also the parameter lists, i.e. types+names of the arguments; and whether they're defaulted (Qt considers these as separate overloads)
** the revision number  (Q_REVISION)
* Q_CLASSINFO metadata
* Q_ENUM and Q_FLAGS
 
=== Can C++26 reflection extract the same information? ===
 
==== Class name ====
Should there be no problems (display_string_of?)
 
==== Parent class name ====
Again, no problems. Can use bases_of and extract the first (or identify the one that gives us access to QObject, etc.)
 
===== Invokables =====
 
===== Properties =====
 
===== Q_CLASSINFO =====
 
===== Q_ENUM / Q_FLAG =====
 
== What needs to be generated? ==
In a nutshell: some static data, the implementation of various things declared by the Q_OBJECT macro, the implementation of the class' metaObject() function, the implementation of signals, ...
 
For instance, for QWidget:<syntaxhighlight lang="cpp" line="1">
namespace {
struct qt_meta_tag_ZN7QWidgetE_t {};
} // unnamed namespace
 
template <> constexpr inline auto QWidget::qt_create_metaobjectdata<qt_meta_tag_ZN7QWidgetE_t>()
{ ... }
 
Q_CONSTINIT const QMetaObject QWidget::staticMetaObject = { ... }
 
void QWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { ... }
 
const QMetaObject *QWidget::metaObject() const { ... }
 
void *QWidget::qt_metacast(const char *_clname) { ... }
 
int QWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { ... }
 
// SIGNAL 0
void QWidget::windowTitleChanged(const QString & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 0, nullptr, _t1);
}
 
// SIGNAL 1
void QWidget::windowIconChanged(const QIcon & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 1, nullptr, _t1);
}
 
// SIGNAL 2
void QWidget::windowIconTextChanged(const QString & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 2, nullptr, _t1);
}
 
// SIGNAL 3
void QWidget::customContextMenuRequested(const QPoint & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 3, nullptr, _t1);
}
</syntaxhighlight>
 
=== JSON output ===
moc also generates a JSON file with metaobject information for the class.
 
== Triggers for moc / buildsystem? ==
moc needs to be run on source files that define classes or namespaces and in which a "trigger keyword" is found: Q_OBJECT, Q_GADGET, Q_NAMESPACE, etc.
moc needs to be run on source files that define classes or namespaces and in which a "trigger keyword" is found: Q_OBJECT, Q_GADGET, Q_NAMESPACE, etc.


Usually, the buildsystem identifies these files, and prepares build rules so that moc is run on them.
Usually, the buildsystem identifies these files, and prepares build rules so that moc is run on them.


The output of moc is some additional .cpp code (some static data, the implementation of various things declared by the Q_OBJECT macro, the implementation of the class' metaObject() function, the implementation of signals, ...)
The output of moc is some additional .cpp code, this code can either get compiled standalone (and linked into the final target) via additional build rules, or directly included by a .cpp file (e.g. #include "moc_foo.cpp") in which case no additional rules are needed.


This code can either get compiled standalone (and linked into the final target) via additional build rules, or directly included by a .cpp file (e.g. #include "moc_foo.cpp") in which case no additional rules are needed.
=== How to "trigger reflection"? ===
The main idea on the table is that we're going to need a macro of some sorts to be placed in a .cpp file. That macro will expand to everything necessary -- defining all the Q_OBJECT-declared stuff by calling consteval functions that apply reflection, extract the necessary data, build tables and the like.


=== How to trigger reflection? ===
Something like this:
The main idea on the table is that we're going to need a macro.
Q_GENERATE_METAOBJECT(Class)
Having to do add this ''manually'' seems annoying though.

Revision as of 00:16, 28 May 2025

This is a WORK IN PROGRESS page to understand the implications of "Reflection for C++" for the future of Qt and moc.

Why?

moc extracts interesting metadata from QObject subclasses / gadgets / namespaces. This metadata is then used at runtime to implement many different QMetaObject facilities.

C++26 will (likely) ship with compile-time reflection. This means that the work that moc does today (as an external tool, with its custom lexer and parser etc.) could be done by the compiler itself. In the future, this may unlock lots of interesting possibilities (e.g. templated QObjects) and reduce technical debt (no need to maintain our own C++ parser for moc).

However, we're not sure how to get there just yet. Some questions that this work aims to answer:

  • Can we replace moc with a pure C++ solution?
    • Is there something missing from standard C++ that we need for moc?
  • How many source code (API) breaks are to be expected?
    • If there's breakage, can porting tools automate the transition?
  • Are there going to be issues with buildsystems?

Reflection for Standard C++: references

Papers that are interesting for Qt:

What needs to be extracted?

moc extracts:

  • the class name
  • the parent class name (following QObject's inheritance chain)
  • properties (Q_PROPERTY)
  • invokables (methods marked with Q_INVOKABLE, Q_SIGNAL, Q_SLOT; may include constructors)
    • for each invokable, also the parameter lists, i.e. types+names of the arguments; and whether they're defaulted (Qt considers these as separate overloads)
    • the revision number (Q_REVISION)
  • Q_CLASSINFO metadata
  • Q_ENUM and Q_FLAGS

Can C++26 reflection extract the same information?

Class name

Should there be no problems (display_string_of?)

Parent class name

Again, no problems. Can use bases_of and extract the first (or identify the one that gives us access to QObject, etc.)

Invokables
Properties
Q_CLASSINFO
Q_ENUM / Q_FLAG

What needs to be generated?

In a nutshell: some static data, the implementation of various things declared by the Q_OBJECT macro, the implementation of the class' metaObject() function, the implementation of signals, ...

For instance, for QWidget:

namespace {
struct qt_meta_tag_ZN7QWidgetE_t {};
} // unnamed namespace

template <> constexpr inline auto QWidget::qt_create_metaobjectdata<qt_meta_tag_ZN7QWidgetE_t>()
{ ... }

Q_CONSTINIT const QMetaObject QWidget::staticMetaObject = { ... }

void QWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { ... }

const QMetaObject *QWidget::metaObject() const { ... }

void *QWidget::qt_metacast(const char *_clname) { ... }

int QWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { ... }

// SIGNAL 0
void QWidget::windowTitleChanged(const QString & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 0, nullptr, _t1);
}

// SIGNAL 1
void QWidget::windowIconChanged(const QIcon & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 1, nullptr, _t1);
}

// SIGNAL 2
void QWidget::windowIconTextChanged(const QString & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 2, nullptr, _t1);
}

// SIGNAL 3
void QWidget::customContextMenuRequested(const QPoint & _t1)
{
    QMetaObject::activate<void>(this, &staticMetaObject, 3, nullptr, _t1);
}

JSON output

moc also generates a JSON file with metaobject information for the class.

Triggers for moc / buildsystem?

moc needs to be run on source files that define classes or namespaces and in which a "trigger keyword" is found: Q_OBJECT, Q_GADGET, Q_NAMESPACE, etc.

Usually, the buildsystem identifies these files, and prepares build rules so that moc is run on them.

The output of moc is some additional .cpp code, this code can either get compiled standalone (and linked into the final target) via additional build rules, or directly included by a .cpp file (e.g. #include "moc_foo.cpp") in which case no additional rules are needed.

How to "trigger reflection"?

The main idea on the table is that we're going to need a macro of some sorts to be placed in a .cpp file. That macro will expand to everything necessary -- defining all the Q_OBJECT-declared stuff by calling consteval functions that apply reflection, extract the necessary data, build tables and the like.

Something like this:

Q_GENERATE_METAOBJECT(Class)

Having to do add this manually seems annoying though.