Q(ObjectBindable)Property: Properly support comparisons

Before, it was possible to do some comparisons due to the implicit
conversion to T. However, that does not work in all contexts.

Fix this by explicitly declaring the comparison operators. We need to
a) use the helper macro directly, as the operartors are only available
   if the underlying type has them and
b) mark the operator as noexcept(false), as capturing a property in a
   binding might allocate.

Fixes: QTBUG-134921
Change-Id: I2855964ba481b9e7778a4a7076528593549910fe
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
Fabian Kosmale 2025-05-26 13:22:07 +02:00
parent dc45850c1e
commit e115c60b6d
4 changed files with 75 additions and 0 deletions

View File

@ -1277,6 +1277,9 @@ QString QPropertyBindingError::description() const
\inmodule QtCore
\brief The QProperty class is a template class that enables automatic property bindings.
\since 6.0
\compares equality
\compareswith equality T
\endcompareswith
\ingroup tools
@ -1486,6 +1489,9 @@ QString QPropertyBindingError::description() const
automatic property bindings for property data stored in QObject derived
classes.
\since 6.0
\compares equality
\compareswith equality T
\endcompareswith
\ingroup tools

View File

@ -384,6 +384,10 @@ public:
#endif
~QProperty() = default;
QT_DECLARE_EQUALITY_OPERATORS_HELPER(QProperty, QProperty, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
QT_DECLARE_EQUALITY_OPERATORS_HELPER(QProperty, T, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(QProperty, T, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
parameter_type value() const
{
d.registerWithCurrentlyEvaluatingBinding();
@ -504,6 +508,18 @@ public:
const QtPrivate::QPropertyBindingData &bindingData() const { return d; }
private:
template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>
friend bool comparesEqual(const QProperty &lhs, const QProperty &rhs)
{
return lhs.value() == rhs.value();
}
template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>
friend bool comparesEqual(const QProperty &lhs, const T &rhs)
{
return lhs.value() == rhs;
}
void notify()
{
d.notifyObservers(this);
@ -1068,6 +1084,10 @@ public:
explicit QObjectBindableProperty(Functor &&f);
#endif
QT_DECLARE_EQUALITY_OPERATORS_HELPER(QObjectBindableProperty, QObjectBindableProperty, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
QT_DECLARE_EQUALITY_OPERATORS_HELPER(QObjectBindableProperty, T, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
QT_DECLARE_EQUALITY_OPERATORS_REVERSED_HELPER(QObjectBindableProperty, T, /* non-constexpr */, noexcept(false), template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>)
parameter_type value() const
{
qGetBindingStorage(owner())->registerDependency(this);
@ -1208,6 +1228,18 @@ public:
return *storage->bindingData(const_cast<ThisType *>(this), true);
}
private:
template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>
friend bool comparesEqual(const QObjectBindableProperty &lhs, const QObjectBindableProperty &rhs)
{
return lhs.value() == rhs.value();
}
template <typename Ty = T, std::enable_if_t<QTypeTraits::has_operator_equal_v<Ty>>* = nullptr>
friend bool comparesEqual(const QObjectBindableProperty &lhs, const T &rhs)
{
return lhs.value() == rhs;
}
void notify(const QtPrivate::QPropertyBindingData *binding)
{
if (binding)

View File

@ -16,4 +16,5 @@ qt_internal_add_test(tst_qproperty
tst_qproperty.cpp
LIBRARIES
Qt::CorePrivate
Qt::TestPrivate
)

View File

@ -7,6 +7,7 @@
#include <qproperty.h>
#include <private/qproperty_p.h>
#include <private/qobject_p.h>
#include <private/qcomparisontesthelper_p.h>
#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_QDOC)
#include <source_location>
@ -82,6 +83,8 @@ private slots:
void compatPropertyNoDobuleNotification();
void compatPropertySignals();
void compareAgainstValueType();
void noFakeDependencies();
#if QT_CONFIG(thread)
void threadSafety();
@ -1735,6 +1738,39 @@ void tst_QProperty::compatPropertySignals()
QCOMPARE(arguments.at(0).toInt(), 42);
}
struct CompareTestObject : QObject{
Q_OBJECT
public:
CompareTestObject(const QVariantList &l) { varList = l; }
Q_OBJECT_BINDABLE_PROPERTY(CompareTestObject, QVariantList, varList)
};
void tst_QProperty::compareAgainstValueType()
{
{
// compile time checks
QTestPrivate::testEqualityOperatorsCompile<QProperty<QVariantList>>();
QTestPrivate::testEqualityOperatorsCompile<QProperty<QVariantList>, QVariantList>();
using ObjectBindableProperty = decltype(std::declval<CompareTestObject>().varList);
QTestPrivate::testEqualityOperatorsCompile<ObjectBindableProperty>();
QTestPrivate::testEqualityOperatorsCompile<ObjectBindableProperty, QVariantList>();
}
QVariantList vl {1, QString(), QByteArray {}};
QProperty<QVariantList> vlProp { vl };
CompareTestObject o { vl };
QCOMPARE_EQ(vl, vlProp);
QCOMPARE_EQ(vl, o.varList);
vl.pop_back();
QCOMPARE_NE(vl, vlProp);
QCOMPARE_NE(vl, o.varList);
}
class FakeDependencyCreator : public QObject
{
Q_OBJECT