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"