Discussion:
[Development] Metatype system in Qt6
Jedrzej Nowacki
2018-10-29 16:39:16 UTC
Permalink
Hi everyone!

While main heat on the mailing list is taken by topic how to encode that we
are nice, friendly and respectful to each other, I would like to show some
side project that I had. It is a proposal for base of metatype system for Qt6.
You can look and comment at it here: https://github.com/nierob/qmetatype. It
is quite fresh and it was rather a storage for functionality ideas. I haven't
tried to compare performance of it to the current system, but for sure it has
more flexibility and I believe, that conceptually it could serve us during
Qt6. Anyway before spending too much time on it I would like to get some early
feedback and questions.

Cheers,
Jędrek
Olivier Goffart
2018-10-29 17:46:18 UTC
Permalink
Post by Jedrzej Nowacki
Hi everyone!
While main heat on the mailing list is taken by topic how to encode that we
are nice, friendly and respectful to each other, I would like to show some
side project that I had. It is a proposal for base of metatype system for Qt6.
You can look and comment at it here: https://github.com/nierob/qmetatype. It
is quite fresh and it was rather a storage for functionality ideas. I haven't
tried to compare performance of it to the current system, but for sure it has
more flexibility and I believe, that conceptually it could serve us during
Qt6. Anyway before spending too much time on it I would like to get some early
feedback and questions.
The discussion in that other thread are not finished, so please wait until
there is consensus before being nice!

But thanks for caring about the QMetaType system.
I had a short look. I think it would be usefull if you already used names
closer to what they are supposed to be. Namespace N, P, are not so nice names.

The idea of using a single function with operation is quite a good idea I like
it. As long as the function takes the typeid as a parameter.
Indeed, I'm thinking about dynamic types that would come from language
bindings: in this situation, while it is easy to allocate memory on the heap,
it is not easy to create a new function pointer for every dynamic type that we
would register.

Regarding the extension, i don't know if it is such a good idea, because you
never know what you can rely on.
say you have a QVariant with some type that comes from some part of your code,
how do you know if you can print it with qDebug, or convert it to string, how
do you register that?
IMHO, there should not be extensions! All operation that we want to make
available for a type should be always available. Using SFINAE to find out if it
is available.

So what are the operation we want on a QMetaType:

- for QVariant, and the queued connections: copy / destruction. Ideally in
place. So we also need the size/alignement to be able to allocate the memory

- for qDebug, we need the QDebug operator <<

- for QDataStream, we need the operator << and >>, and an unique identifier
that stay the same. Currently, this is the type id for builtin types, and the
name for custom type. I suggest we use the name, if we want to keep
compatibility with old steam, we will need to keep a mapping from old type id,
to new name.

As for the name, we can indeed find a way to extract it from parsing it from
__PRETTY_FUNC__ as you do. It would work on every compiler we support i guess,
but we need spcial code for that as it is not really standard.
In Qt5, we also need the name for the string-based connection syntax. However,
I believe we can do that without relying on the name "as-seen-by-moc", if the
moc generated code always register the types. (this is already almost the case)

- What about QSequentialIterable / QAssociativeIterable? We also need
something there.
Post by Jedrzej Nowacki
The whole registry is kept behind a mutex and it is very central, the mutex
usage actually shows on profilers.
This used to be the case before Qt 5.7, but since then, QReadWriteMutex was
greatly optimized, does it still show in the profiler?

You will need that anyway, because relying on a static variable in a function
template does not work on MSVC as far as i know. (Last time i tried was a long
time ago, and they had different address and value in different libraries)
That's why we will probably need to initialize it with a name lookup.

It is probably still a good idea to register builtin type at compile time,
since we want to save in registering time. There are some data structures out
there with compile time hash table that can be extended at runtime.


So the early feedback i can give on your code is that it is a bit more
complicated than necessary with the extension. and i think it can be simplified.
--
Olivier

Woboq - Qt services and support - https://woboq.com - https://code.woboq.org
Jedrzej Nowacki
2018-10-30 11:38:40 UTC
Permalink
Post by Olivier Goffart
Post by Jedrzej Nowacki
Hi everyone!
While main heat on the mailing list is taken by topic how to encode that we
are nice, friendly and respectful to each other, I would like to show some
side project that I had. It is a proposal for base of metatype system for
https://github.com/nierob/qmetatype. It is quite fresh and it was rather
a storage for functionality ideas. I haven't tried to compare performance
of it to the current system, but for sure it has more flexibility and I
believe, that conceptually it could serve us during Qt6. Anyway before
spending too much time on it I would like to get some early feedback and
questions.
The discussion in that other thread are not finished, so please wait until
there is consensus before being nice!
Well I prefer publish it now and work in the open. I do not expect everyone
jumping to the topic , dropping everything else, but at least some people can
look at the code :-) There is no rush, for me it is an achieved milestone. Now
the harder work slowly may happen.
Post by Olivier Goffart
But thanks for caring about the QMetaType system.
Thanks for looking at the code!
Post by Olivier Goffart
I had a short look. I think it would be usefull if you already used names
closer to what they are supposed to be. Namespace N, P, are not so nice names.
N is next, P is private, kind of my personal standard.

Action point for me:
- updated P to QtPrivate
- see how many name clashes I will get after removing N.
Post by Olivier Goffart
The idea of using a single function with operation is quite a good idea I
like it. As long as the function takes the typeid as a parameter.
Indeed, I'm thinking about dynamic types that would come from language
bindings: in this situation, while it is easy to allocate memory on the
heap, it is not easy to create a new function pointer for every dynamic
type that we would register.
Let's postpone that discussion to a moment that we agree on the extensions.
Post by Olivier Goffart
Regarding the extension, i don't know if it is such a good idea, because you
never know what you can rely on.
say you have a QVariant with some type that comes from some part of your
code, how do you know if you can print it with qDebug, or convert it to
string, how do you register that?
IMHO, there should not be extensions! All operation that we want to make
available for a type should be always available. Using SFINAE to find out if
it is available.
Extensions in that respect do not change anything, because it is about error
handling. How to inform a user that a type is not qdebug stream-able or can
not be converted to QString. In the current and the data driven approach you
would need to check if the right function pointer is null or not and on top of
this the error handling still would need to happen as the call itself could
fail. With the proposed solution you have only one point in which you handle
potential errors, just after the metatype call.

So let's return to QVariant concern that you expressed. I believe that if you
plan to use a feature you need to ensure that you can. So I would propose
QVariant constructor to look more or less like qVariantFromValue, it would do
the "registration" it needs. It is in sync with QObject registering own
properties types.

SFINAE would be used in extensions, as I showed in https://github.com/nierob/
qmetatype/blob/master/extensions/streams.h, still error handling is missing
there. Actually I think it is wrong layer for detection, creating dummy
handlers is wrong.

Action points for me:
- show in a better way that extensions can be added lazily
- improve error handling in metatype call
- highlight more common set of operations that would be used if qTypeId<T>()
is called
- move stream SFINAE detection a bit higher in the code stack
Post by Olivier Goffart
- for QVariant, and the queued connections: copy / destruction. Ideally in
place. So we also need the size/alignement to be able to allocate the memory
- for qDebug, we need the QDebug operator <<
- for QDataStream, we need the operator << and >>, and an unique identifier
that stay the same. Currently, this is the type id for builtin types, and
the name for custom type. I suggest we use the name, if we want to keep
compatibility with old steam, we will need to keep a mapping from old type
id, to new name.
I'm pretty sure that you are aware that the list is far from being complete,
so I will not neat pick on this, but how you make sure that every future use
case would satisfied? Hiding functionality behind a function and extensible
data struct allow _us_ to extend it if _we_ believe that costs are worth the
gain, but it doesn't solve problem for 3rdparty code. We do see the problem
even in our modules that needs to keep a sibling mapping from typeid to some
functions, in particular dbus and qml(?).
Post by Olivier Goffart
As for the name, we can indeed find a way to extract it from parsing it from
__PRETTY_FUNC__ as you do. It would work on every compiler we support i
guess, but we need spcial code for that as it is not really standard.
Yes, it is compiler specific, but some version of the trick would work on all
supported compilers, I think. Even if not we could fallback to typeid() and
require RTTI on these potentially few weaker compilers, so my feeling is that
it is safe to use it.
Post by Olivier Goffart
In Qt5, we also need the name for the string-based connection syntax.
However, I believe we can do that without relying on the name
"as-seen-by-moc", if the moc generated code always register the types.
(this is already almost the case)
Why almost? Is it that because of forward declared types?
Post by Olivier Goffart
- What about QSequentialIterable / QAssociativeIterable? We also need
something there.
Nothing really, currently it works through conversions. We could keep the way
or really extract a new API that allows to do same thing. It is slightly
orthogonal, unless I miss something.

Action point for me:
- prototype conversions
Post by Olivier Goffart
Post by Jedrzej Nowacki
The whole registry is kept behind a mutex and it is very central, the mutex
usage actually shows on profilers.
This used to be the case before Qt 5.7, but since then, QReadWriteMutex was
greatly optimized, does it still show in the profiler?
Hmm true, I do not know. The list was created from the top of my head.

Action point for me:
- remove the point
Post by Olivier Goffart
You will need that anyway, because relying on a static variable in a
function template does not work on MSVC as far as i know. (Last time i
tried was a long time ago, and they had different address and value in
different libraries) That's why we will probably need to initialize it with
a name lookup.
Ugh, that is an ugly problem. This https://github.com/nierob/qmetatype/blob/
master/metatype_impl.h#L80 one really requires the unification of variables.
You are right we can workaround it with a global registry and name lookup,
but... oh.
Post by Olivier Goffart
It is probably still a good idea to register builtin type at compile time,
since we want to save in registering time. There are some data structures
out there with compile time hash table that can be extended at runtime.
Yes, in addition we will need to have QMetaType::Type mapping anyway to keep
SC.

Action point for me:
- prototype the mapping
Post by Olivier Goffart
So the early feedback i can give on your code is that it is a bit more
complicated than necessary with the extension. and i think it can be simplified.
Because you are skipping one of the features that I would like to have. To be
discussed ;-)

Thank you!
Jędrek
Olivier Goffart
2018-10-30 12:29:35 UTC
Permalink
Hi,


On 10/30/18 12:38 PM, Jedrzej Nowacki wrote:
[...]
Post by Jedrzej Nowacki
Extensions in that respect do not change anything, because it is about error
handling. How to inform a user that a type is not qdebug stream-able
Not really important. I just want to do
qDebug() << my_variant;
and have some useful information if possible. This is just for printf-style
debugging. I don't want to add a line before such as:
qRegisterDebugOperator<MyType>(); // maybe my_variant contains MyType so i
need to register it.

I expect that if something goes into the QVariant, it has everything.
Post by Jedrzej Nowacki
or can not be converted to QString.
{QMetaType/QVariant}::canConvert, but let's talk about conversion later.
Post by Jedrzej Nowacki
In the current and the data driven approach you
would need to check if the right function pointer is null or not and on top of
this the error handling still would need to happen as the call itself could
fail. With the proposed solution you have only one point in which you handle
potential errors, just after the metatype call.
I don't understand why, internally, handling a null function pointer (or check
the return code of a metacall function) is a problem. Also i do not see how the
proposed extension system solve this. Please elaborate.
Post by Jedrzej Nowacki
So let's return to QVariant concern that you expressed. I believe that if you
plan to use a feature you need to ensure that you can. So I would propose
QVariant constructor to look more or less like qVariantFromValue, it would do
the "registration" it needs. It is in sync with QObject registering own
properties types.
Right, exactly!
So QVariant would call qMetaTypeId<T>() which would take care that the type is
registered, with all features.

[...]
Post by Jedrzej Nowacki
Post by Olivier Goffart
- for QVariant, and the queued connections: copy / destruction. Ideally in
place. So we also need the size/alignement to be able to allocate the memory
- for qDebug, we need the QDebug operator <<
- for QDataStream, we need the operator << and >>, and an unique identifier
that stay the same. Currently, this is the type id for builtin types, and
the name for custom type. I suggest we use the name, if we want to keep
compatibility with old steam, we will need to keep a mapping from old type
id, to new name.
I'm pretty sure that you are aware that the list is far from being complete,
Yes. I might have forgot a couple of things. Some flags, registering the
QMetaObject, conversion function, indeed.

Speaking of conversion. I think you agree that this huge conversion martix to
try to convert from and to anything is a bad design we should review.
I've heard you in the past claiming that you want to remove any conversion.
I think we probably should just enable conversion to/from some basic primitive
type: string and numbers.
I'm not sure this is usefull to let QVariant have the ability to convert from a
QImage to a QPixmap.
Post by Jedrzej Nowacki
so I will not neat pick on this, but how you make sure that every future use
case would satisfied?
Say we decide, in Qt 6.4, to add the std::iostream operators while this was no
planed before: we just add them. I don't see the problem.
We need to be carefull about binary compatibility and types registered in older
application, but that is not a big issue.

We can also add whatever in Qt 5.x lifetime. We added some data to the
QMetatype within Qt 5.x lifetime. This is not a problem.
Post by Jedrzej Nowacki
Hiding functionality behind a function and extensible
data struct allow _us_ to extend it if _we_ believe that costs are worth the
gain, but it doesn't solve problem for 3rdparty code. We do see the problem
even in our modules that needs to keep a sibling mapping from typeid to some
functions, in particular dbus and qml(?).
Here you make a good case.
If I understand correctly, you don't want dbus and qml to have themself a global:
QHash<QMetaTypeId, QmlTypeExtenstion> typeRegistery; // guarded by typeMutex

In this respect, there is indeed value for extensions.
As long as the basic stuff are not extensions it is fine.
(I would hope that neither Qml nor dbus need extensions though)

[...]
Post by Jedrzej Nowacki
Post by Olivier Goffart
In Qt5, we also need the name for the string-based connection syntax.
However, I believe we can do that without relying on the name
"as-seen-by-moc", if the moc generated code always register the types.
(this is already almost the case)
Why almost? Is it that because of forward declared types?
Because it only work for type that moc can determine to be automatically
registrable. Ideally, all types should be registerable.

But moc has a conservative aproch here, doing it to eager can have source
incompatibilities issue

moc-ng [https://github.com/woboq/moc-ng] already registers all types that can
be registered. And I guess moc should do the same.

verdigris [https://github.com/woboq/verdigris] also register all types.

We have the problem with pointer to forward declared class. That can cause ODR
violation and is indeed a problem.
Post by Jedrzej Nowacki
Post by Olivier Goffart
You will need that anyway, because relying on a static variable in a
function template does not work on MSVC as far as i know. (Last time i
tried was a long time ago, and they had different address and value in
different libraries) That's why we will probably need to initialize it with
a name lookup.
Ugh, that is an ugly problem. This https://github.com/nierob/qmetatype/blob/
master/metatype_impl.h#L80 one really requires the unification of variables.
You are right we can workaround it with a global registry and name lookup,
but... oh.
I know, ...

Previous work:
https://bugreports.qt.io/browse/QTBUG-19137
https://codereview.qt-project.org/2106
Yeah.. that's old. Too bad that did not go in Qt5
--
Olivier

Woboq - Qt services and support - https://woboq.com - https://code.woboq.org
Uwe Rathmann
2018-10-30 13:20:55 UTC
Permalink
Post by Olivier Goffart
In Qt5, we also need the name for the string-based connection syntax.
I'm not sure if I'm ontopic for the Metatype system with my comment, so
please excuse me if I'm hijacking this thread for a moment.

I have this code:

https://github.com/uwerat/qskinny/blob/master/src/common/QskMetaFunction.h
https://github.com/uwerat/qskinny/blob/master/src/common/
QskMetaInvokable.h

This code is for situations, where registering callbacks ( slots ) is
needed, but without having a signal or a sender, that is a QObject. F.e
like what you have with QTimer::singleShot.

Our most important use case is a local bus system, that connects events
coming from a system using Sun RPC with slots or property values. As we
have several 100 pages each having lots of controls we don't want to
create dummy sender Objects only to be able to call a slot and I ended up
with diving into template programming myself.

Would it be possible to make all this template based stuff for setting up
callbacks more independent from being used with signals in Qt6 ?

ciao,
Uwe

Loading...