Skip to content

Instantly share code, notes, and snippets.

@hasselmm
Created December 18, 2023 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hasselmm/4e24b8e1ef4b12b8aec28675f1209621 to your computer and use it in GitHub Desktop.
Save hasselmm/4e24b8e1ef4b12b8aec28675f1209621 to your computer and use it in GitHub Desktop.
André Pönitz Property
#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
#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();
}
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
#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
@hasselmm
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment