From de3e9708898da6a6480e973308d228b8dcc3b139 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Fri, 3 Mar 2023 11:20:06 +0100 Subject: [PATCH] Implement [variant.get] for QVariant [1/2]: get_if() QVariant supports non-default-constructible payloads, in principle (QTBUG-105140). And fromValue() works with such types, but value() insists on providing a wide contract and therefore accidentally requires default-constructible. We can now invent other "Qt-ish" API like optional::value_or() or a value() that returns optional, but we should first get the interface in that generic code must use, and which at the same time is the most versatile, because it gives write access to the element stored in the variant: [variant.get], consisting of get_if(), get(), and holds_alternative(). The latter is the same as get_if() != nullptr, so we won't provide it. This first patch implements get_if(), adds test for it. As a Hidden Friend supposed to be called with explicit template arguments, we run into the problem that wg21.link/P0846 solved for C++20. Add the usual work-around, and check it works. The ChangeLog will be on the last patch. Task-number: QTBUG-111598 Change-Id: I23f57ea2de3946944810c5552c68a7a3060a44f2 Reviewed-by: Fabian Kosmale Reviewed-by: Thiago Macieira --- src/corelib/kernel/qvariant.cpp | 21 ++- src/corelib/kernel/qvariant.h | 20 +++ .../corelib/kernel/qvariant/tst_qvariant.cpp | 159 ++++++++++++++++++ 3 files changed, 198 insertions(+), 2 deletions(-) diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index b25e855276..704b46e1c1 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -2333,7 +2333,7 @@ QPartialOrdering QVariant::compare(const QVariant &lhs, const QVariant &rhs) Returns a pointer to the contained object as a generic void* that cannot be written to. - \sa QMetaType + \sa get_if(), QMetaType */ /*! @@ -2343,7 +2343,7 @@ QPartialOrdering QVariant::compare(const QVariant &lhs, const QVariant &rhs) This function detaches the QVariant. When called on a \l{isNull}{null-QVariant}, the QVariant will not be null after the call. - \sa QMetaType + \sa get_if(), QMetaType */ void *QVariant::data() { @@ -2353,6 +2353,23 @@ void *QVariant::data() return const_cast(constData()); } +/*! + \since 6.6 + \fn template const T* QVariant::get_if(const QVariant *v) + \fn template T* QVariant::get_if(QVariant *v) + + If \a v contains an object of type \c T, returns a pointer to the contained + object, otherwise returns \nullptr. + + The overload taking a mutable \a v detaches \a v: When called on a + \l{isNull()}{null} \a v with matching type \c T, \a v will not be null + after the call. + + These functions are provided for compatibility with \c{std::variant}. + + \sa data() +*/ + /*! Returns \c true if this is a null variant, false otherwise. diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 990a2a0884..50b7e8c2bd 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -24,6 +24,8 @@ QT_BEGIN_NAMESPACE +QT_ENABLE_P0846_SEMANTICS_FOR(get_if) + class QBitArray; class QDataStream; class QDate; @@ -474,6 +476,24 @@ private: } QDebug qdebugHelper(QDebug) const; #endif + + template + friend T *get_if(QVariant *v) noexcept + { + // data() will detach from is_null, returning non-nullptr + if (!v || v->d.type() != QMetaType::fromType()) + return nullptr; + return static_cast(v->data()); + } + template + friend const T *get_if(const QVariant *v) noexcept + { + // (const) data() will not detach from is_null, return nullptr + if (!v || v->d.is_null || v->d.type() != QMetaType::fromType()) + return nullptr; + return static_cast(v->data()); + } + template friend inline T qvariant_cast(const QVariant &); protected: diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index 28eb45660e..e5ffe14c97 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -5,6 +5,34 @@ #include +// don't assume +template +constexpr inline bool my_is_same_v = false; +template +constexpr inline bool my_is_same_v = true; + +#define CHECK_IMPL(func, arg, Variant, cvref, R) \ + static_assert(my_is_same_v(std::declval< Variant cvref >())), R cvref >) + +#define CHECK_GET_IF(Variant, cvref) \ + CHECK_IMPL(get_if, int, Variant, cvref *, int) + +CHECK_GET_IF(QVariant, /* unadorned */); +CHECK_GET_IF(QVariant, const); + +// check for a type derived from QVariant: + +struct MyVariant : QVariant +{ + using QVariant::QVariant; +}; + +CHECK_GET_IF(MyVariant, /* unadorned */); +CHECK_GET_IF(MyVariant, const); + +#undef CHECK_GET_IF +#undef CHECK_IMPL + #include // Please stick to alphabetic order. @@ -44,7 +72,10 @@ #include #include +using namespace Qt::StringLiterals; + class CustomNonQObject; +struct NonDefaultConstructible; template struct QVariantFromValueCompiles @@ -333,7 +364,19 @@ private slots: void constructFromIncompatibleMetaType(); void copyNonDefaultConstructible(); + void getIf_int() { getIf_impl(42); } + void getIf_QString() { getIf_impl(u"string"_s); }; + void getIf_NonDefaultConstructible(); + private: + using StdVariant = std::variant; + template + void getIf_impl(T t) const; void dataStream_data(QDataStream::Version version); void loadQVariantFromDataStream(QDataStream::Version version); void saveQVariantFromDataStream(QDataStream::Version version); @@ -5654,5 +5697,121 @@ void tst_QVariant::copyNonDefaultConstructible() QCOMPARE(var2, var); } +void tst_QVariant::getIf_NonDefaultConstructible() +{ + getIf_impl(NonDefaultConstructible{42}); +} + +template +T mutate(const T &t) { return t + t; } +template <> +NonDefaultConstructible mutate(const NonDefaultConstructible &t) +{ + return NonDefaultConstructible{t.i + t.i}; +} + +template +QVariant make_null_QVariant_of_type() +{ + return QVariant(QMetaType::fromType()); +} + +template +void tst_QVariant::getIf_impl(T t) const +{ + QVariant v = QVariant::fromValue(t); + + QVariant null; + QVERIFY(null.isNull()); + + [[maybe_unused]] + QVariant nulT; + if constexpr (std::is_default_constructible_v) { + // typed null QVariants don't work with non-default-constuctable types + nulT = make_null_QVariant_of_type(); + QVERIFY(nulT.isNull()); + } + + QVariant date = QDate(2023, 3, 3); + static_assert(!std::is_same_v); + + // for behavioral comparison: + StdVariant stdn = {}, stdv = t; + + // returns nullptr on type mismatch: + { + // const + QCOMPARE_EQ(get_if(&std::as_const(stdn)), nullptr); + QCOMPARE_EQ(get_if(&std::as_const(date)), nullptr); + // mutable + QCOMPARE_EQ(get_if(&stdn), nullptr); + QCOMPARE_EQ(get_if(&date), nullptr); + } + + // returns nullptr on null variant (QVariant only): + { + QCOMPARE_EQ(get_if(&std::as_const(null)), nullptr); + QCOMPARE_EQ(get_if(&null), nullptr); + if constexpr (std::is_default_constructible_v) { + // const access return nullptr + QCOMPARE_EQ(get_if(&std::as_const(nulT)), nullptr); + // but mutable access makes typed null QVariants non-null (like data()) + QCOMPARE_NE(get_if(&nulT), nullptr); + QVERIFY(!nulT.isNull()); + nulT = make_null_QVariant_of_type(); // reset to null state + } + } + + // const access: + { + auto ps = get_if(&std::as_const(stdv)); + static_assert(std::is_same_v); + QCOMPARE_NE(ps, nullptr); + QCOMPARE_EQ(*ps, t); + + auto pv = get_if(&std::as_const(v)); + static_assert(std::is_same_v); + QCOMPARE_NE(pv, nullptr); + QCOMPARE_EQ(*pv, t); + } + + // mutable access: + { + T t2 = mutate(t); + + auto ps = get_if(&stdv); + static_assert(std::is_same_v); + QCOMPARE_NE(ps, nullptr); + QCOMPARE_EQ(*ps, t); + *ps = t2; + auto ps2 = get_if(&stdv); + QCOMPARE_NE(ps2, nullptr); + QCOMPARE_EQ(*ps2, t2); + + auto pv = get_if(&v); + static_assert(std::is_same_v); + QCOMPARE_NE(pv, nullptr); + QCOMPARE_EQ(*pv, t); + *pv = t2; + auto pv2 = get_if(&v); + QCOMPARE_NE(pv2, nullptr); + QCOMPARE_EQ(*pv2, t2); + + // typed null QVariants become non-null (data() behavior): + if constexpr (std::is_default_constructible_v) { + QVERIFY(nulT.isNull()); + auto pn = get_if(&nulT); + QVERIFY(!nulT.isNull()); + static_assert(std::is_same_v); + QCOMPARE_NE(pn, nullptr); + QCOMPARE_EQ(*pn, T{}); + *pn = t2; + auto pn2 = get_if(&nulT); + QCOMPARE_NE(pn2, nullptr); + QCOMPARE_EQ(*pn2, t2); + } + } +} + QTEST_MAIN(tst_QVariant) #include "tst_qvariant.moc"