QMetaType: Support custom unary converters with optional<To> return type

To indicate success of a conversion, the public API has previously only
supported registering member functions of the form To (From::*)(bool *).
When adding custom converters for types that cannot be modified, this is
usually not a possibility.
As an alternative, this patch adds support for std::optional in the
UnaryFunction overload of QMetaType::registerConverter. If the returned
optional has no value, the conversion is considered failed.

Task-number: QTBUG-92902
Change-Id: Ibac52d2cb9b5a2457081b4bebb0def1f03e3c55d
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Arno Rehn 2022-09-23 16:31:47 +02:00
parent 38f8d531a2
commit 7a7051b58f
4 changed files with 74 additions and 16 deletions

View File

@ -1,6 +1,7 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QJsonObject>
#include <QMetaType>
#include <QString>
@ -50,5 +51,12 @@ int main() {
QMetaType::registerConverter<CustomStringType, QString>([](const CustomStringType &str) {
return QString::fromUtf8(str.data());
});
QMetaType::registerConverter<QJsonValue, QPointF>(
[](const QJsonValue &value) -> std::optional<QPointF> {
const auto object = value.toObject();
if (!object.contains("x") || !object.contains("y"))
return std::nullopt; // The conversion fails if the required properties are missing
return QPointF{object["x"].toDouble(), object["y"].toDouble()};
});
//! [unaryfunc]
}

View File

@ -1721,7 +1721,8 @@ Q_GLOBAL_STATIC(QMetaTypeMutableViewRegistry, customTypesMutableViewRegistry)
to type To in the meta type system. Returns \c true if the registration succeeded, otherwise false.
\a function must take an instance of type \c From and return an instance of \c To. It can be a function
pointer, a lambda or a functor object.
pointer, a lambda or a functor object. Since Qt 6.5, the \a function can also return an instance of
\c std::optional<To> to be able to indicate failed conversions.
\snippet qmetatype/registerConverters.cpp unaryfunc
*/

View File

@ -24,6 +24,8 @@
#include <list>
#include <map>
#include <functional>
#include <optional>
#include <type_traits>
#ifdef Bool
#error qmetatype.h must be included before any header file that defines Bool
@ -600,7 +602,15 @@ public:
auto converter = [function = std::move(function)](const void *from, void *to) -> bool {
const From *f = static_cast<const From *>(from);
To *t = static_cast<To *>(to);
*t = function(*f);
auto &&r = function(*f);
if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<decltype(r)>>,
std::optional<To>>) {
if (!r)
return false;
*t = *std::forward<decltype(r)>(r);
} else {
*t = std::forward<decltype(r)>(r);
}
return true;
};
return registerConverterImpl<From, To>(std::move(converter), fromType, toType);

View File

@ -30,6 +30,30 @@ struct ConvertFunctor
}
};
template<typename T>
struct OptionalWrapper
{
std::optional<T> operator()(const T& t) const
{
if (!CustomConvertibleType::s_ok)
return std::nullopt;
return t;
}
};
template<typename From>
struct ConvertFunctorWithOptional
{
std::optional<CustomConvertibleType> operator()(const From& f) const
{
if (!CustomConvertibleType::s_ok)
return std::nullopt;
return CustomConvertibleType(QVariant::fromValue(f));
}
};
template<typename From, typename To>
bool hasRegisteredConverterFunction()
{
@ -81,6 +105,7 @@ void customTypeNotYetConvertible()
testCustomTypeNotYetConvertible<QLineF, CustomConvertibleType>();
testCustomTypeNotYetConvertible<QChar, CustomConvertibleType>();
testCustomTypeNotYetConvertible<CustomConvertibleType, CustomConvertibleType2>();
testCustomTypeNotYetConvertible<CustomConvertibleType, std::optional<CustomConvertibleType>>();
}
void registerCustomTypeConversions()
@ -99,20 +124,22 @@ void registerCustomTypeConversions()
QVERIFY((QMetaType::registerConverter<CustomConvertibleType, QLine>(&CustomConvertibleType::convert<QLine>)));
QVERIFY((QMetaType::registerConverter<CustomConvertibleType, QLineF>(&CustomConvertibleType::convertOk<QLineF>)));
QVERIFY((QMetaType::registerConverter<CustomConvertibleType, QChar>(&CustomConvertibleType::convert<QChar>)));
QVERIFY((QMetaType::registerConverter<QString, CustomConvertibleType>(ConvertFunctor<QString>())));
QVERIFY((QMetaType::registerConverter<QString, CustomConvertibleType>(ConvertFunctorWithOptional<QString>())));
QVERIFY((QMetaType::registerConverter<bool, CustomConvertibleType>(ConvertFunctor<bool>())));
QVERIFY((QMetaType::registerConverter<int, CustomConvertibleType>(ConvertFunctor<int>())));
QVERIFY((QMetaType::registerConverter<int, CustomConvertibleType>(ConvertFunctorWithOptional<int>())));
QVERIFY((QMetaType::registerConverter<double, CustomConvertibleType>(ConvertFunctor<double>())));
QVERIFY((QMetaType::registerConverter<float, CustomConvertibleType>(ConvertFunctor<float>())));
QVERIFY((QMetaType::registerConverter<float, CustomConvertibleType>(ConvertFunctorWithOptional<float>())));
QVERIFY((QMetaType::registerConverter<QRect, CustomConvertibleType>(ConvertFunctor<QRect>())));
QVERIFY((QMetaType::registerConverter<QRectF, CustomConvertibleType>(ConvertFunctor<QRectF>())));
QVERIFY((QMetaType::registerConverter<QRectF, CustomConvertibleType>(ConvertFunctorWithOptional<QRectF>())));
QVERIFY((QMetaType::registerConverter<QPoint, CustomConvertibleType>(ConvertFunctor<QPoint>())));
QVERIFY((QMetaType::registerConverter<QPointF, CustomConvertibleType>(ConvertFunctor<QPointF>())));
QVERIFY((QMetaType::registerConverter<QPointF, CustomConvertibleType>(ConvertFunctorWithOptional<QPointF>())));
QVERIFY((QMetaType::registerConverter<QSize, CustomConvertibleType>(ConvertFunctor<QSize>())));
QVERIFY((QMetaType::registerConverter<QSizeF, CustomConvertibleType>(ConvertFunctor<QSizeF>())));
QVERIFY((QMetaType::registerConverter<QSizeF, CustomConvertibleType>(ConvertFunctorWithOptional<QSizeF>())));
QVERIFY((QMetaType::registerConverter<QLine, CustomConvertibleType>(ConvertFunctor<QLine>())));
QVERIFY((QMetaType::registerConverter<QLineF, CustomConvertibleType>(ConvertFunctor<QLineF>())));
QVERIFY((QMetaType::registerConverter<QLineF, CustomConvertibleType>(ConvertFunctorWithOptional<QLineF>())));
QVERIFY((QMetaType::registerConverter<QChar, CustomConvertibleType>(ConvertFunctor<QChar>())));
QVERIFY((QMetaType::registerConverter<CustomConvertibleType, std::optional<CustomConvertibleType>>(OptionalWrapper<CustomConvertibleType>())));
QVERIFY((QMetaType::registerConverter<CustomConvertibleType, CustomConvertibleType2>()));
QTest::ignoreMessage(QtWarningMsg, "Type conversion already registered from type CustomConvertibleType to type CustomConvertibleType2");
QVERIFY((!QMetaType::registerConverter<CustomConvertibleType, CustomConvertibleType2>()));
@ -171,7 +198,7 @@ void tst_QMetaType::convertCustomType()
QCOMPARE(v.toString(), ok ? testQString : QString());
QCOMPARE(v.value<QString>(), ok ? testQString : QString());
QVERIFY(CustomConvertibleType::s_value.canConvert<CustomConvertibleType>());
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toString()), testQString);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toString()), ok ? testQString : QString());
QFETCH(bool, testBool);
CustomConvertibleType::s_value = testBool;
@ -183,7 +210,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testInt;
QCOMPARE(v.toInt(), ok ? testInt : 0);
QCOMPARE(v.value<int>(), ok ? testInt : 0);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toInt()), testInt);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toInt()), ok ? testInt : 0);
QFETCH(double, testDouble);
CustomConvertibleType::s_value = testDouble;
@ -195,7 +222,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testFloat;
QCOMPARE(v.toFloat(), ok ? testFloat : 0.0);
QCOMPARE(v.value<float>(), ok ? testFloat : 0.0);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toFloat()), testFloat);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toFloat()), ok ? testFloat : 0);
QFETCH(QRect, testQRect);
CustomConvertibleType::s_value = testQRect;
@ -207,7 +234,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testQRectF;
QCOMPARE(v.toRectF(), ok ? testQRectF : QRectF());
QCOMPARE(v.value<QRectF>(), ok ? testQRectF : QRectF());
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toRectF()), testQRectF);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toRectF()), ok ? testQRectF : QRectF());
QFETCH(QPoint, testQPoint);
CustomConvertibleType::s_value = testQPoint;
@ -219,7 +246,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testQPointF;
QCOMPARE(v.toPointF(), ok ? testQPointF : QPointF());
QCOMPARE(v.value<QPointF>(), ok ? testQPointF : QPointF());
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toPointF()), testQPointF);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toPointF()), ok ? testQPointF : QPointF());
QFETCH(QSize, testQSize);
CustomConvertibleType::s_value = testQSize;
@ -231,7 +258,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testQSizeF;
QCOMPARE(v.toSizeF(), ok ? testQSizeF : QSizeF());
QCOMPARE(v.value<QSizeF>(), ok ? testQSizeF : QSizeF());
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toSizeF()), testQSizeF);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toSizeF()), ok ? testQSizeF : QSizeF());
QFETCH(QLine, testQLine);
CustomConvertibleType::s_value = testQLine;
@ -243,7 +270,7 @@ void tst_QMetaType::convertCustomType()
CustomConvertibleType::s_value = testQLineF;
QCOMPARE(v.toLineF(), ok ? testQLineF : QLineF());
QCOMPARE(v.value<QLineF>(), ok ? testQLineF : QLineF());
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toLineF()), testQLineF);
QCOMPARE((CustomConvertibleType::s_value.value<CustomConvertibleType>().m_foo.toLineF()), ok ? testQLineF : QLineF());
QFETCH(QChar, testQChar);
CustomConvertibleType::s_value = testQChar;
@ -255,6 +282,18 @@ void tst_QMetaType::convertCustomType()
QVERIFY(v.canConvert(QMetaType(::qMetaTypeId<CustomConvertibleType2>())));
QCOMPARE(v.value<CustomConvertibleType2>().m_foo, testCustom.m_foo);
// Check that converters that actually convert to std::optional<T> are not
// taken to indicate success or failure of the conversion. In these cases,
// the conversion must always succeed, even if the converter has returned a
// nullopt.
v = QVariant::fromValue(testCustom);
QVERIFY(v.canConvert(QMetaType::fromType<std::optional<CustomConvertibleType>>()));
QVERIFY(v.convert(QMetaType::fromType<std::optional<CustomConvertibleType>>()));
QCOMPARE(v.value<std::optional<CustomConvertibleType>>().has_value(), ok);
if (ok) {
QCOMPARE(v.value<std::optional<CustomConvertibleType>>().value().m_foo, testCustom.m_foo);
}
QFETCH(DerivedGadgetType, testDerived);
v = QVariant::fromValue(testDerived);
QCOMPARE(v.metaType(), QMetaType::fromType<DerivedGadgetType>());