Created
December 18, 2023 21:05
-
-
Save hasselmm/4e24b8e1ef4b12b8aec28675f1209621 to your computer and use it in GitHub Desktop.
André Pönitz Property
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef APROPERTY_H | |
#define APROPERTY_H | |
#include <utility> | |
namespace Private { | |
template<class T, typename V> | |
using NotifyMemberFunction = void (T::*)(V); | |
template<class T, typename V> | |
struct NotifyMemberTrait | |
{ | |
using TargetType = T; | |
using ValueType = V; | |
}; | |
template<class T, typename V> | |
constexpr auto inspect(NotifyMemberFunction<T, V>) | |
{ | |
return NotifyMemberTrait<T, V>{}; | |
} | |
template<auto notify> | |
struct NotifierInfo | |
{ | |
using NotifierType = decltype(inspect(notify)); | |
using TargetType = typename NotifierType::TargetType; | |
using ValueType = typename NotifierType::ValueType; | |
}; | |
} // namespace | |
template<typename T> | |
class Property | |
{ | |
public: | |
using ValueType = T; | |
Property() noexcept = default; | |
Property(ValueType initialValue) noexcept | |
: m_value{std::move(initialValue)} | |
{} | |
constexpr auto get() const noexcept { return m_value; } | |
auto operator()() const noexcept { return get(); } | |
protected: | |
ValueType m_value; | |
}; | |
template<typename> class Setter; | |
template<auto notify> | |
class Notifying : public Property<typename Private::NotifierInfo<notify>::ValueType> | |
{ | |
public: | |
using TargetType = typename Private::NotifierInfo<notify>::TargetType; | |
using ValueType = typename Private::NotifierInfo<notify>::ValueType; | |
Notifying(TargetType *target, ValueType initialValue) noexcept | |
: Property<ValueType>{std::move(initialValue)} | |
, m_target{target} | |
{} | |
protected: | |
void set(ValueType newValue) | |
{ | |
if (std::exchange(m_value, std::move(newValue)) != m_value) | |
(m_target->*notify)(m_value); | |
} | |
Notifying &operator=(ValueType newValue) | |
{ | |
set(std::move(newValue)); | |
return *this; | |
} | |
private: | |
TargetType *const m_target; | |
using Property<ValueType>::m_value; | |
friend Setter<ValueType>; | |
friend TargetType; | |
}; | |
template<typename T> | |
class Setter | |
{ | |
public: | |
using ValueType = T; | |
template<auto notify> | |
Setter(Notifying<notify> *property) | |
: m_property{property} | |
, m_setValue{[](void *p, ValueType newValue) { | |
const auto property = static_cast<Notifying<notify> *>(p); | |
property->set(std::move(newValue)); | |
}} | |
{} | |
void operator()(ValueType newValue) const | |
{ | |
m_setValue(m_property, std::move(newValue)); | |
} | |
private: | |
// Using std::function<void(T)> would give Setter<T> a size of 32 bytes on x86_64. | |
// By manually doing the type-erasure the size of Setter<T> is reduced to 16 bytes. | |
void *m_property; | |
void (* m_setValue)(void *, ValueType); | |
}; | |
#endif // APROPERTY_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "testobject.h" | |
#include <QCoreApplication> | |
#include <QSignalSpy> | |
#include <QVariant> | |
class Application : public QCoreApplication | |
{ | |
public: | |
using QCoreApplication::QCoreApplication; | |
int run(); | |
}; | |
#define SHOW(What) qInfo() << #What " =>" << (What) | |
int Application::run() | |
{ | |
auto a = TestObject{}; | |
auto observingSpy = QSignalSpy{&a, &TestObject::observingChanged}; | |
auto modifiableSpy = QSignalSpy{&a, &TestObject::modifiableChanged}; | |
SHOW(a.constant()); | |
SHOW(a.property("constant")); | |
SHOW(a.observing()); | |
SHOW(a.property("observing")); | |
SHOW(observingSpy); | |
SHOW(a.modifiable()); | |
SHOW(a.property("modifiable")); | |
SHOW(modifiableSpy); | |
a.modifyObserving(); | |
a.setModifiable("modified"); | |
SHOW(a.observing()); | |
SHOW(a.property("observing")); | |
SHOW(observingSpy); | |
SHOW(a.modifiable()); | |
SHOW(a.property("modifiable")); | |
SHOW(modifiableSpy); | |
SHOW(sizeof(Property<QString>)); | |
SHOW(sizeof(Notifying<&TestObject::modifiableChanged>)); | |
SHOW(sizeof(Setter<QString>)); | |
SHOW(sizeof(std::function<void(QString)>)); | |
return 0; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
return Application{argc, argv}.run(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
a.constant() => "I am constant" | |
a.property("constant") => QVariant(QString, "I am constant") | |
a.observing() => "I am observing" | |
a.property("observing") => QVariant(QString, "I am observing") | |
observingSpy => QList() | |
a.modifiable() => "I am modifiable" | |
a.property("modifiable") => QVariant(QString, "I am modifiable") | |
modifiableSpy => QList() | |
a.observing() => "I have changed" | |
a.property("observing") => QVariant(QString, "I have changed") | |
observingSpy => QList(QList(QVariant(QString, "I have changed"))) | |
a.modifiable() => "modified" | |
a.property("modifiable") => QVariant(QString, "modified") | |
modifiableSpy => QList(QList(QVariant(QString, "modified"))) | |
sizeof(Property<QString>) => 24 | |
sizeof(Notifying<&TestObject::modifiableChanged>) => 32 | |
sizeof(Setter<QString>) => 16 | |
sizeof(std::function<void(QString)>) => 32 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef TESTOBJECT_H | |
#define TESTOBJECT_H | |
#include "aproperty.h" | |
#include <QObject> | |
#include <QString> | |
class TestObject : public QObject | |
{ | |
Q_OBJECT | |
Q_PROPERTY(QString constant READ constant CONSTANT FINAL) | |
Q_PROPERTY(QString observing READ observing NOTIFY observingChanged FINAL) | |
Q_PROPERTY(QString modifiable READ modifiable WRITE setModifiable NOTIFY modifiableChanged FINAL) | |
public: | |
using QObject::QObject; | |
void modifyObserving() { observing = "I have changed"; } | |
signals: | |
void observingChanged(QString observing); | |
void modifiableChanged(QString modifiable); | |
public: | |
Property<QString> constant = u"I am constant"_qs; | |
Notifying<&TestObject::observingChanged> observing = {this, u"I am observing"_qs}; | |
Notifying<&TestObject::modifiableChanged> modifiable = {this, u"I am modifiable"_qs}; | |
const Setter<QString> setModifiable = {&modifiable}; | |
}; | |
#endif // TESTOBJECT_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inspired by https://lists.qt-project.org/pipermail/development/2023-December/044807.html