Verdigris makes trait classes possibles ; would trait objects be possible too ?
jcelerier opened this issue · 3 comments
This is not an issue but rather a discussion about what is possible to reach with the power of constexprified moc :p
Thanks to templated QObject support, it is now possible to do :
template<typename T>
class Trait1 : public T {
W_OBJECT(Trait1)
public:
void blah(int x) W_SIGNAL(blah, x);
};
template<typename T>
class Trait2 : public T {
W_OBJECT(Trait2)
public:
void zob(float f1, float f2) W_SIGNAL(zob, f1, f2);
};
class Object1 : public QObject {
W_OBJECT(Object1)
};
class Object2 : public QObject {
W_OBJECT(Object2)
};
using ActualObject1 = Trait1<Object1>;
using ActualObject2 = Trait1<Trait2<Object2>>;
which can make some designs simpler and more efficient instead of the usual way which is marking the signals as virtual in a trait class which does not inherit from QObject. However it can also in my experience quickly turn into the 6th circle of template hell.
My question is: is it in the realm of possibilities to construct a similar behaviour but either with direct inheritance or composition ?
e.g. something which would look like:
class Trait {
W_LIGHT_OBJECT(Trait)
public:
void zob(float f1, float f2) W_SIGNAL(zob, f1, f2);
};
class ActualObject1 : public QObject {
W_OBJECT(ActualObject1)
public:
Trait trait{*this}; // methods of the trait are registered in the QObject's metaobject
};
or
class ActualObject2 : public QObject, public Trait, public Trait2 {
W_OBJECT(ActualObject2)
};
in both cases the point being the reduction of the amount of QObject / QObjectPrivate / QMetaObject in the system.
The QObject runtime expects only single inheritance, so the trait can't themself have QMetaObject.
But base class could have method and properties, which could be imported into another object using the W_INTERFACE
macro.
However this would not work for signals. The signal implementation need to know in which metaobject it relates to, as well as its id within that metaobject.
One way i can think of to do that would be using CRTP:
template <typename W_ThisObject>
class Trait {
W_TRAIT(Trait); // I don't actually know if we really need this
void zob(float f1, float f2)
// the implementation of W_SIGNAL would expend to something like
{
QMetaObject::activate(static_cast<W_ThisObject*>(this), &W_ThisObject::staticMetaObject,
W_ThisObject::signalStartFor(this) + signalIndex, ... );
}
};
class ActualObject : public QObject, public Trait<ActualObject> {
W_OBJECT(ActualObject)
W_INTERFACE(Trait)
};
The W_INTERFACE would then pull the signals and proiperties from the Trait class inside the ActualObject, and it would also declare a static constexpr int signalStartFor(Trait*)
But then, since we would anyway need to use CRTP, there is not so much advantages over normal inheritence as you describe.
static constexpr int signalStartFor(Trait*)
hmmm.. how would this be implementable without having something that is able to list the bases ? eg given
class ActualObject
: public QObject
, public Trait<ActualObject>
, public Trait2<ActualObject>
, public Trait3<ActualObject>
{
W_OBJECT(ActualObject)
W_INTERFACE(Trait)
W_INTERFACE(Trait2)
W_INTERFACE(Trait3)
};
how can W_INTERFACE(Trait3) know that int signalStartFor(Trait3*)
== num_signals(in this object) + num_signals(Trait) + num_signals(Trait2)
?
@jcelerier because W_INTERFACE(Trait3) is after W_INTERFACE(Trait2) which add some state to the class, and let it know the offset.