diff options
Diffstat (limited to 'src/corelib/kernel/qvariant.cpp')
-rw-r--r-- | src/corelib/kernel/qvariant.cpp | 785 |
1 files changed, 457 insertions, 328 deletions
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 8f413b52e0..92a44c462b 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -3,7 +3,7 @@ // Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qvariant.h" +#include "qvariant_p.h" #include "qbitarray.h" #include "qbytearray.h" #include "qdatastream.h" @@ -47,6 +47,8 @@ #include "qline.h" #endif +#include <memory> + #include <cmath> #include <float.h> #include <cstring> @@ -57,24 +59,31 @@ using namespace Qt::StringLiterals; namespace { // anonymous used to hide QVariant handlers -/*! - \internal - */ +static qlonglong qMetaTypeNumberBySize(const QVariant::Private *d) +{ + switch (d->typeInterface()->size) { + case 1: + return d->get<signed char>(); + case 2: + return d->get<short>(); + case 4: + return d->get<int>(); + case 8: + return d->get<qlonglong>(); + } + Q_UNREACHABLE_RETURN(0); +} + static qlonglong qMetaTypeNumber(const QVariant::Private *d) { - switch (d->type().id()) { + switch (d->typeInterface()->typeId) { case QMetaType::Int: - return d->get<int>(); case QMetaType::LongLong: - return d->get<qlonglong>(); case QMetaType::Char: - return qlonglong(d->get<char>()); case QMetaType::SChar: - return qlonglong(d->get<signed char>()); case QMetaType::Short: - return qlonglong(d->get<short>()); case QMetaType::Long: - return qlonglong(d->get<long>()); + return qMetaTypeNumberBySize(d); case QMetaType::Float: return qRound64(d->get<float>()); case QMetaType::Double: @@ -86,54 +95,46 @@ static qlonglong qMetaTypeNumber(const QVariant::Private *d) return d->get<QCborValue>().toInteger(); #endif } - Q_ASSERT(false); - return 0; + Q_UNREACHABLE_RETURN(0); } static qulonglong qMetaTypeUNumber(const QVariant::Private *d) { - switch (d->type().id()) { - case QMetaType::UInt: - return d->get<unsigned int>(); - case QMetaType::ULongLong: - return d->get<qulonglong>(); - case QMetaType::UChar: + switch (d->typeInterface()->size) { + case 1: return d->get<unsigned char>(); - case QMetaType::UShort: + case 2: return d->get<unsigned short>(); - case QMetaType::ULong: - return d->get<unsigned long>(); + case 4: + return d->get<unsigned int>(); + case 8: + return d->get<qulonglong>(); } - Q_ASSERT(false); - return 0; + Q_UNREACHABLE_RETURN(0); } -static qlonglong qConvertToNumber(const QVariant::Private *d, bool *ok, bool allowStringToBool = false) +static std::optional<qlonglong> qConvertToNumber(const QVariant::Private *d, bool allowStringToBool = false) { - *ok = true; - - switch (uint(d->type().id())) { + bool ok; + switch (d->typeInterface()->typeId) { case QMetaType::QString: { const QString &s = d->get<QString>(); - qlonglong l = s.toLongLong(ok); - if (*ok) + if (qlonglong l = s.toLongLong(&ok); ok) return l; if (allowStringToBool) { - if (s == "false"_L1 || s == "0"_L1) { - *ok = true; + if (s == "false"_L1 || s == "0"_L1) return 0; - } - if (s == "true"_L1 || s == "1"_L1) { - *ok = true; + if (s == "true"_L1 || s == "1"_L1) return 1; - } } - return 0; + return std::nullopt; } case QMetaType::QChar: return d->get<QChar>().unicode(); case QMetaType::QByteArray: - return d->get<QByteArray>().toLongLong(ok); + if (qlonglong l = d->get<QByteArray>().toLongLong(&ok); ok) + return l; + return std::nullopt; case QMetaType::Bool: return qlonglong(d->get<bool>()); #ifndef QT_BOOTSTRAPPED @@ -158,47 +159,42 @@ static qlonglong qConvertToNumber(const QVariant::Private *d, bool *ok, bool all case QMetaType::ULongLong: case QMetaType::UInt: case QMetaType::UChar: + case QMetaType::Char16: + case QMetaType::Char32: case QMetaType::UShort: case QMetaType::ULong: - return qlonglong(qMetaTypeUNumber(d)); } - QMetaType typeInfo = d->type(); - if (typeInfo.flags() & QMetaType::IsEnumeration - || d->type().id() == QMetaType::QCborSimpleType) { - switch (typeInfo.sizeOf()) { - case 1: - return d->get<signed char>(); - case 2: - return d->get<short>(); - case 4: - return d->get<int>(); - case 8: - return d->get<qlonglong>(); - } - } + if (d->typeInterface()->flags & QMetaType::IsEnumeration + || d->typeInterface()->typeId == QMetaType::QCborSimpleType) + return qMetaTypeNumberBySize(d); - *ok = false; - return Q_INT64_C(0); + return std::nullopt; } -static qreal qConvertToRealNumber(const QVariant::Private *d, bool *ok) +static std::optional<double> qConvertToRealNumber(const QVariant::Private *d) { - *ok = true; - switch (uint(d->type().id())) { + bool ok; + switch (d->typeInterface()->typeId) { case QMetaType::QString: - return d->get<QString>().toDouble(ok); + if (double r = d->get<QString>().toDouble(&ok); ok) + return r; + return std::nullopt; case QMetaType::Double: - return qreal(d->get<double>()); + return d->get<double>(); case QMetaType::Float: - return qreal(d->get<float>()); + return double(d->get<float>()); + case QMetaType::Float16: + return double(d->get<qfloat16>()); case QMetaType::ULongLong: case QMetaType::UInt: case QMetaType::UChar: + case QMetaType::Char16: + case QMetaType::Char32: case QMetaType::UShort: case QMetaType::ULong: - return qreal(qMetaTypeUNumber(d)); + return double(qMetaTypeUNumber(d)); #ifndef QT_BOOTSTRAPPED case QMetaType::QCborValue: return d->get<QCborValue>().toDouble(); @@ -207,7 +203,9 @@ static qreal qConvertToRealNumber(const QVariant::Private *d, bool *ok) #endif default: // includes enum conversion as well as invalid types - return qreal(qConvertToNumber(d, ok)); + if (std::optional<qlonglong> l = qConvertToNumber(d)) + return double(*l); + return std::nullopt; } } @@ -235,24 +233,22 @@ static bool isValidMetaTypeForVariant(const QtPrivate::QMetaTypeInterface *iface return true; } -template <typename F> static QVariant::PrivateShared * -customConstructShared(size_t size, size_t align, F &&construct) -{ - struct Deleter { - void operator()(QVariant::PrivateShared *p) const - { QVariant::PrivateShared::free(p); } - }; +enum CustomConstructMoveOptions { + UseCopy, // custom construct uses the copy ctor unconditionally + // future option: TryMove: uses move ctor if available, else copy ctor + ForceMove, // custom construct use the move ctor (which must exist) +}; - // this is exception-safe - std::unique_ptr<QVariant::PrivateShared, Deleter> ptr; - ptr.reset(QVariant::PrivateShared::create(size, align)); - construct(ptr->data()); - return ptr.release(); -} +enum CustomConstructNullabilityOption { + MaybeNull, // copy might be null, might be non-null + NonNull, // copy is guarantueed to be non-null + // future option: AlwaysNull? +}; // the type of d has already been set, but other field are not set +template <CustomConstructMoveOptions moveOption = UseCopy, CustomConstructNullabilityOption nullability = MaybeNull> static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant::Private *d, - const void *copy) + std::conditional_t<moveOption == ForceMove, void *, const void *> copy) { using namespace QtMetaTypePrivate; Q_ASSERT(iface); @@ -261,6 +257,10 @@ static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant Q_ASSERT(isCopyConstructible(iface)); Q_ASSERT(isDestructible(iface)); Q_ASSERT(copy || isDefaultConstructible(iface)); + if constexpr (moveOption == ForceMove) + Q_ASSERT(isMoveConstructible(iface)); + if constexpr (nullability == NonNull) + Q_ASSERT(copy != nullptr); // need to check for nullptr_t here, as this can get called by fromValue(nullptr). fromValue() uses // std::addressof(value) which in this case returns the address of the nullptr object. @@ -270,11 +270,17 @@ static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant if (QVariant::Private::canUseInternalSpace(iface)) { d->is_shared = false; if (!copy && !iface->defaultCtr) - return; // trivial default constructor, we've already memset - construct(iface, d->data.data, copy); + return; // trivial default constructor and it's OK to build in 0-filled storage, which we've already done + if constexpr (moveOption == ForceMove && nullability == NonNull) + moveConstruct(iface, d->data.data, copy); + else + construct(iface, d->data.data, copy); } else { d->data.shared = customConstructShared(iface->size, iface->alignment, [=](void *where) { - construct(iface, where, copy); + if constexpr (moveOption == ForceMove && nullability == NonNull) + moveConstruct(iface, where, copy); + else + construct(iface, where, copy); }); d->is_shared = true; } @@ -310,59 +316,6 @@ static QVariant::Private clonePrivate(const QVariant::Private &other) } // anonymous used to hide QVariant handlers -inline QVariant::PrivateShared *QVariant::PrivateShared::create(size_t size, size_t align) -{ - size += sizeof(PrivateShared); - if (align > sizeof(PrivateShared)) { - // The alignment is larger than the alignment we can guarantee for the pointer - // directly following PrivateShared, so we need to allocate some additional - // memory to be able to fit the object into the available memory with suitable - // alignment. - size += align - sizeof(PrivateShared); - } - void *data = operator new(size); - auto *ps = new (data) QVariant::PrivateShared(); - ps->offset = int(((quintptr(ps) + sizeof(PrivateShared) + align - 1) & ~(align - 1)) - quintptr(ps)); - return ps; -} - -inline void QVariant::PrivateShared::free(PrivateShared *p) -{ - p->~PrivateShared(); - operator delete(p); -} - -inline QVariant::Private::Private(const QtPrivate::QMetaTypeInterface *iface) noexcept - : is_shared(false), is_null(false), packedType(quintptr(iface) >> 2) -{ - Q_ASSERT((quintptr(iface) & 0x3) == 0); -} - -template <typename T> inline -QVariant::Private::Private(std::piecewise_construct_t, const T &t) - : is_shared(!CanUseInternalSpace<T>), is_null(std::is_same_v<T, std::nullptr_t>) -{ - // confirm noexceptness - static constexpr bool isNothrowQVariantConstructible = noexcept(QVariant(t)); - static constexpr bool isNothrowCopyConstructible = std::is_nothrow_copy_constructible_v<T>; - static constexpr bool isNothrowCopyAssignable = std::is_nothrow_copy_assignable_v<T>; - - const QtPrivate::QMetaTypeInterface *iface = QtPrivate::qMetaTypeInterfaceForType<T>(); - Q_ASSERT((quintptr(iface) & 0x3) == 0); - packedType = quintptr(iface) >> 2; - - if constexpr (CanUseInternalSpace<T>) { - static_assert(isNothrowQVariantConstructible == isNothrowCopyConstructible); - static_assert(isNothrowQVariantConstructible == isNothrowCopyAssignable); - new (data.data) T(t); - } else { - static_assert(!isNothrowQVariantConstructible); // we allocate memory, even if T doesn't - data.shared = customConstructShared(sizeof(T), alignof(T), [=](void *where) { - new (where) T(t); - }); - } -} - /*! \class QVariant \inmodule QtCore @@ -371,6 +324,7 @@ QVariant::Private::Private(std::piecewise_construct_t, const T &t) \ingroup objectmodel \ingroup shared + \compares equality Because C++ forbids unions from including types that have non-default constructors or destructors, most interesting Qt @@ -563,23 +517,18 @@ void QVariant::create(int type, const void *copy) */ void QVariant::create(QMetaType type, const void *copy) { - *this = QVariant(type, copy); + *this = QVariant::fromMetaType(type, copy); } /*! \fn QVariant::~QVariant() Destroys the QVariant and the contained object. - - Note that subclasses that reimplement clear() should reimplement - the destructor to call clear(). This destructor calls clear(), but - because it is the destructor, QVariant::clear() is called rather - than a subclass's clear(). */ QVariant::~QVariant() { - if ((d.is_shared && !d.data.shared->ref.deref()) || (!d.is_shared)) + if (!d.is_shared || !d.data.shared->ref.deref()) customClear(&d); } @@ -596,6 +545,106 @@ QVariant::QVariant(const QVariant &p) } /*! + \fn template <typename T, typename... Args, QVariant::if_constructible<T, Args...> = true> QVariant::QVariant(std::in_place_type_t<T>, Args&&... args) noexcept(is_noexcept_constructible<q20::remove_cvref_t<T>, Args...>::value) + + \since 6.6 + Constructs a new variant containing a value of type \c T. The contained + value is is initialized with the arguments + \c{std::forward<Args>(args)...}. + + This overload only participates in overload resolution if \c T can be + constructed from \a args. + + This constructor is provided for STL/std::any compatibility. + + \overload + */ + +/*! + + \fn template <typename T, typename U, typename... Args, QVariant::if_constructible<T, std::initializer_list<U> &, Args...> = true> explicit QVariant::QVariant(std::in_place_type_t<T>, std::initializer_list<U> il, Args&&... args) noexcept(is_noexcept_constructible<q20::remove_cvref_t<T>, std::initializer_list<U> &, Args... >::value) + + \since 6.6 + \overload + This overload exists to support types with constructors taking an + \c initializer_list. It behaves otherwise equivalent to the + non-initializer list \c{in_place_type_t} overload. +*/ + + +/*! + \fn template <typename T, typename... Args, QVariant::if_constructible<T, Args...> = true> QVariant::emplace(Args&&... args) + + \since 6.6 + Replaces the object currently held in \c{*this} with an object of + type \c{T}, constructed from \a{args}\c{...}. If \c{*this} was non-null, + the previously held object is destroyed first. + If possible, this method will reuse memory allocated by the QVariant. + Returns a reference to the newly-created object. + */ + +/*! + \fn template <typename T, typename U, typename... Args, QVariant::if_constructible<T, std::initializer_list<U> &, Args...> = true> QVariant::emplace(std::initializer_list<U> list, Args&&... args) + + \since 6.6 + \overload + This overload exists to support types with constructors taking an + \c initializer_list. It behaves otherwise equivalent to the + non-initializer list overload. +*/ + +QVariant::QVariant(std::in_place_t, QMetaType type) : d(type.iface()) +{ + // we query the metatype instead of detecting it at compile time + // so that we can change relocatability of internal types + if (!Private::canUseInternalSpace(type.iface())) { + d.data.shared = PrivateShared::create(type.sizeOf(), type.alignOf()); + d.is_shared = true; + } +} + +/*! + \internal + Returns a pointer to data suitable for placement new + of an object of type \a type + Changes the variant's metatype to \a type + */ +void *QVariant::prepareForEmplace(QMetaType type) +{ + /* There are two cases where we can reuse the existing storage + (1) The new type fits in QVariant's SBO storage + (2) We are using the externally allocated storage, the variant is + detached, and the new type fits into the existing storage. + In all other cases (3), we cannot reuse the storage. + */ + auto typeFits = [&] { + auto newIface = type.iface(); + auto oldIface = d.typeInterface(); + auto newSize = PrivateShared::computeAllocationSize(newIface->size, newIface->alignment); + auto oldSize = PrivateShared::computeAllocationSize(oldIface->size, oldIface->alignment); + return newSize <= oldSize; + }; + if (Private::canUseInternalSpace(type.iface())) { // (1) + clear(); + d.packedType = quintptr(type.iface()) >> 2; + return d.data.data; + } else if (d.is_shared && isDetached() && typeFits()) { // (2) + QtMetaTypePrivate::destruct(d.typeInterface(), d.data.shared->data()); + // compare QVariant::PrivateShared::create + const auto ps = d.data.shared; + const auto align = type.alignOf(); + ps->offset = PrivateShared::computeOffset(ps, align); + d.packedType = quintptr(type.iface()) >> 2; + return ps->data(); + } + // (3) + QVariant newVariant(std::in_place, type); + swap(newVariant); + // const cast is safe, we're in a non-const method + return const_cast<void *>(d.storage()); +} + +/*! \fn QVariant::QVariant(const QString &val) noexcept Constructs a new variant with a string value, \a val. @@ -604,7 +653,8 @@ QVariant::QVariant(const QVariant &p) /*! \fn QVariant::QVariant(QLatin1StringView val) - Constructs a new variant with a string value, \a val. + Constructs a new variant with a QString value from the Latin-1 + string viewed by \a val. */ /*! @@ -861,26 +911,27 @@ QVariant::QVariant(const QVariant &p) */ /*! - Constructs variant of type \a type, and initializes with - \a copy if \a copy is not \nullptr. + Constructs a variant of type \a type, and initializes it with + a copy of \c{*copy} if \a copy is not \nullptr (in which case, \a copy + must point to an object of type \a type). - Note that you have to pass the address of the variable you want stored. + Note that you have to pass the address of the object you want stored. Usually, you never have to use this constructor, use QVariant::fromValue() instead to construct variants from the pointer types represented by \c QMetaType::VoidStar, and \c QMetaType::QObjectStar. - If \a type does not support copy and default construction, the variant will - be invalid. + If \a type does not support copy construction and \a copy is not \nullptr, + the variant will be invalid. Similarly, if \a copy is \nullptr and + \a type does not support default construction, the variant will be + invalid. - \sa QVariant::fromValue(), QMetaType::Type + \sa QVariant::fromMetaType, QVariant::fromValue(), QMetaType::Type */ -QVariant::QVariant(QMetaType type, const void *copy) : d(type.iface()) +QVariant::QVariant(QMetaType type, const void *copy) + : d() { - if (isValidMetaTypeForVariant(type.iface(), copy)) - customConstruct(type.iface(), &d, copy); - else - d = {}; + *this = fromMetaType(type, copy); } QVariant::QVariant(int val) noexcept : d(std::piecewise_construct_t{}, val) {} @@ -892,7 +943,9 @@ QVariant::QVariant(double val) noexcept : d(std::piecewise_construct_t{}, val) { QVariant::QVariant(float val) noexcept : d(std::piecewise_construct_t{}, val) {} QVariant::QVariant(const QByteArray &val) noexcept : d(std::piecewise_construct_t{}, val) {} +#ifndef QT_BOOTSTRAPPED QVariant::QVariant(const QBitArray &val) noexcept : d(std::piecewise_construct_t{}, val) {} +#endif QVariant::QVariant(const QString &val) noexcept : d(std::piecewise_construct_t{}, val) {} QVariant::QVariant(QChar val) noexcept : d(std::piecewise_construct_t{}, val) {} QVariant::QVariant(const QStringList &val) noexcept : d(std::piecewise_construct_t{}, val) {} @@ -1039,7 +1092,8 @@ void QVariant::detach() Q_ASSERT(isValidMetaTypeForVariant(d.typeInterface(), constData())); Private dd(d.typeInterface()); - customConstruct(d.typeInterface(), &dd, constData()); + // null variant is never shared; anything else is NonNull + customConstruct<UseCopy, NonNull>(d.typeInterface(), &dd, constData()); if (!d.data.shared->ref.deref()) customClear(&d); d.data.shared = dd.data.shared; @@ -1068,7 +1122,7 @@ const char *QVariant::typeName() const */ void QVariant::clear() { - if ((d.is_shared && !d.data.shared->ref.deref()) || (!d.is_shared)) + if (!d.is_shared || !d.data.shared->ref.deref()) customClear(&d); d = {}; } @@ -1085,7 +1139,7 @@ void QVariant::clear() /*! \fn QVariant::Type QVariant::nameToType(const char *name) - \deprecated [6.0] Use \c QMetaType.fromName(name).id() instead + \deprecated [6.0] Use \c QMetaType::fromName(name).id() instead Converts the string representation of the storage type given in \a name, to its enum representation. @@ -1328,9 +1382,21 @@ void QVariant::save(QDataStream &s) const /*! \since 4.4 + \relates QVariant Reads a variant \a p from the stream \a s. + \note If the stream contains types that aren't the built-in ones (see \l + QMetaType::Type), those types must be registered using qRegisterMetaType() + or QMetaType::registerType() before the variant can be properly loaded. If + an unregistered type is found, QVariant will set the corrupt flag in the + stream, stop processing and print a warning. For example, for QList<int> + it would print the following: + + \quotation + QVariant::load: unknown user type with name QList<int> + \endquotation + \sa{Serializing Qt Data Types}{Format of the QDataStream operators} */ QDataStream &operator>>(QDataStream &s, QVariant &p) @@ -1341,6 +1407,7 @@ QDataStream &operator>>(QDataStream &s, QVariant &p) /*! Writes a variant \a p to the stream \a s. + \relates QVariant \sa{Serializing Qt Data Types}{Format of the QDataStream operators} */ @@ -1351,12 +1418,14 @@ QDataStream &operator<<(QDataStream &s, const QVariant &p) } /*! \fn QDataStream& operator>>(QDataStream &s, QVariant::Type &p) + \relates QVariant \deprecated [6.0] Stream QMetaType::Type instead. Reads a variant type \a p in enum representation from the stream \a s. */ /*! \fn QDataStream& operator<<(QDataStream &s, const QVariant::Type p) + \relates QVariant \deprecated [6.0] Stream QMetaType::Type instead. Writes a variant type \a p to the stream \a s. @@ -1406,8 +1475,13 @@ QString QVariant::toString() const } /*! - Returns the variant as a QMap<QString, QVariant> if the variant - has type() \l QMetaType::QVariantMap; otherwise returns an empty map. + Returns the variant as a QVariantMap if the variant has type() \l + QMetaType::QVariantMap. If it doesn't, QVariant will attempt to + convert the type to a map and then return it. This will succeed for + any type that has registered a converter to QVariantMap or which was + declared as a associative container using + \l{Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE}. If none of those + conditions are true, this function will return an empty map. \sa canConvert(), convert() */ @@ -1772,6 +1846,7 @@ QChar QVariant::toChar() const return qvariant_cast<QChar>(*this); } +#ifndef QT_BOOTSTRAPPED /*! Returns the variant as a QBitArray if the variant has userType() \l QMetaType::QBitArray; otherwise returns an empty bit array. @@ -1782,6 +1857,7 @@ QBitArray QVariant::toBitArray() const { return qvariant_cast<QBitArray>(*this); } +#endif // QT_BOOTSTRAPPED template <typename T> inline T qNumVariantToHelper(const QVariant::Private &d, bool *ok) @@ -1957,9 +2033,13 @@ qreal QVariant::toReal(bool *ok) const } /*! - Returns the variant as a QVariantList if the variant has userType() - \l QMetaType::QVariantList or \l QMetaType::QStringList; otherwise returns - an empty list. + Returns the variant as a QVariantList if the variant has userType() \l + QMetaType::QVariantList. If it doesn't, QVariant will attempt to convert + the type to a list and then return it. This will succeed for any type that + has registered a converter to QVariantList or which was declared as a + sequential container using \l{Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE}. If + none of those conditions are true, this function will return an empty + list. \sa canConvert(), convert() */ @@ -2073,9 +2153,9 @@ bool QVariant::view(int type, void *ptr) } /*! - \fn bool QVariant::operator==(const QVariant &v1, const QVariant &v2) + \fn bool QVariant::operator==(const QVariant &lhs, const QVariant &rhs) - Returns \c true if \a v1 and \a v2 are equal; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false. QVariant uses the equality operator of the type() contained to check for equality. @@ -2099,9 +2179,9 @@ bool QVariant::view(int type, void *ptr) */ /*! - \fn bool QVariant::operator!=(const QVariant &v1, const QVariant &v2) + \fn bool QVariant::operator!=(const QVariant &lhs, const QVariant &rhs) - Returns \c false if \a v1 and \a v2 are equal; otherwise returns \c true. + Returns \c false if \a lhs and \a rhs are equal; otherwise returns \c true. QVariant uses the equality operator of the type() contained to check for equality. @@ -2128,6 +2208,8 @@ static bool qIsNumericType(uint tp) Q_UINT64_C(1) << QMetaType::Double | Q_UINT64_C(1) << QMetaType::Float | Q_UINT64_C(1) << QMetaType::Char | + Q_UINT64_C(1) << QMetaType::Char16 | + Q_UINT64_C(1) << QMetaType::Char32 | Q_UINT64_C(1) << QMetaType::SChar | Q_UINT64_C(1) << QMetaType::UChar | Q_UINT64_C(1) << QMetaType::Short | @@ -2143,33 +2225,52 @@ static bool qIsNumericType(uint tp) static bool qIsFloatingPoint(uint tp) { - return tp == QMetaType::Double || tp == QMetaType::Float; + return tp == QMetaType::Double || tp == QMetaType::Float || tp == QMetaType::Float16; } -static int normalizeLowerRanks(uint tp) +static bool canBeNumericallyCompared(const QtPrivate::QMetaTypeInterface *iface1, + const QtPrivate::QMetaTypeInterface *iface2) { - static const qulonglong numericTypeBits = - Q_UINT64_C(1) << QMetaType::Bool | - Q_UINT64_C(1) << QMetaType::Char | - Q_UINT64_C(1) << QMetaType::SChar | - Q_UINT64_C(1) << QMetaType::UChar | - Q_UINT64_C(1) << QMetaType::Short | - Q_UINT64_C(1) << QMetaType::UShort; - return numericTypeBits & (Q_UINT64_C(1) << tp) ? uint(QMetaType::Int) : tp; -} + if (!iface1 || !iface2) + return false; -static int normalizeLong(uint tp) -{ - const uint IntType = sizeof(long) == sizeof(int) ? QMetaType::Int : QMetaType::LongLong; - const uint UIntType = sizeof(ulong) == sizeof(uint) ? QMetaType::UInt : QMetaType::ULongLong; - return tp == QMetaType::Long ? IntType : - tp == QMetaType::ULong ? UIntType : tp; + // We don't need QMetaType::id() here because the type Id is always stored + // directly for all built-in types. + bool isNumeric1 = qIsNumericType(iface1->typeId); + bool isNumeric2 = qIsNumericType(iface2->typeId); + + // if they're both numeric (or QString), then they can be compared + if (isNumeric1 && isNumeric2) + return true; + + bool isEnum1 = iface1->flags & QMetaType::IsEnumeration; + bool isEnum2 = iface2->flags & QMetaType::IsEnumeration; + + // if both are enums, we can only compare if they are the same enum + // (the language does allow comparing two different enum types, but that's + // usually considered poor coding and produces a warning) + if (isEnum1 && isEnum2) + return QMetaType(iface1) == QMetaType(iface2); + + // if one is an enum and the other is a numeric, we can compare too + if (isEnum1 && isNumeric2) + return true; + if (isNumeric1 && isEnum2) + return true; + + // we need at least one enum and one numeric... + return false; } -static int numericTypePromotion(uint t1, uint t2) +static int numericTypePromotion(const QtPrivate::QMetaTypeInterface *iface1, + const QtPrivate::QMetaTypeInterface *iface2) { - Q_ASSERT(qIsNumericType(t1)); - Q_ASSERT(qIsNumericType(t2)); + Q_ASSERT(canBeNumericallyCompared(iface1, iface2)); + + // We don't need QMetaType::id() here because the type Id is always stored + // directly for the types we're comparing against below. + uint t1 = iface1->typeId; + uint t2 = iface2->typeId; if ((t1 == QMetaType::Bool && t2 == QMetaType::QString) || (t2 == QMetaType::Bool && t1 == QMetaType::QString)) @@ -2193,144 +2294,86 @@ static int numericTypePromotion(uint t1, uint t2) if (qIsFloatingPoint(t1) || qIsFloatingPoint(t2)) return QMetaType::QReal; + auto isUnsigned = [](uint tp) { + // only types for which sizeof(T) >= sizeof(int); lesser ones promote to int + return tp == QMetaType::ULongLong || tp == QMetaType::ULong || + tp == QMetaType::UInt || tp == QMetaType::Char32; + }; + bool isUnsigned1 = isUnsigned(t1); + bool isUnsigned2 = isUnsigned(t2); + // integral rules: - // for all platforms we support, int can always hold the values of lower-ranked types - t1 = normalizeLowerRanks(t1); - t2 = normalizeLowerRanks(t2); - - // normalize long / ulong: in all platforms we run, they're either the same as int or as long long - t1 = normalizeLong(t1); - t2 = normalizeLong(t2); - - // implement the other rules - // the four possibilities are Int, UInt, LongLong and ULongLong - // if any of the two is ULongLong, then it wins (highest rank, unsigned) - // otherwise, if one of the two is LongLong, then the other is either LongLong too or lower-ranked - // otherwise, if one of the two is UInt, then the other is either UInt too or Int - if (t1 == QMetaType::ULongLong || t2 == QMetaType::ULongLong) + // 1) if either type is a 64-bit unsigned, compare as 64-bit unsigned + if (isUnsigned1 && iface1->size > sizeof(int)) + return QMetaType::ULongLong; + if (isUnsigned2 && iface2->size > sizeof(int)) return QMetaType::ULongLong; - if (t1 == QMetaType::LongLong || t2 == QMetaType::LongLong) + + // 2) if either type is 64-bit, compare as 64-bit signed + if (iface1->size > sizeof(int) || iface2->size > sizeof(int)) return QMetaType::LongLong; - if (t1 == QMetaType::UInt || t2 == QMetaType::UInt) + + // 3) if either type is 32-bit unsigned, compare as 32-bit unsigned + if (isUnsigned1 || isUnsigned2) return QMetaType::UInt; + + // 4) otherwise, just do int promotion return QMetaType::Int; } -static bool integralEquals(uint promotedType, const QVariant::Private *d1, const QVariant::Private *d2) +template <typename Numeric> static QPartialOrdering spaceShip(Numeric lhs, Numeric rhs) { - // use toLongLong to retrieve the data, it gets us all the bits - bool ok; - qlonglong l1 = qConvertToNumber(d1, &ok, promotedType == QMetaType::Bool); - if (!ok) - return false; - - qlonglong l2 = qConvertToNumber(d2, &ok, promotedType == QMetaType::Bool); - if (!ok) - return false; - - if (promotedType == QMetaType::Bool) - return bool(l1) == bool(l2); - if (promotedType == QMetaType::Int) - return int(l1) == int(l2); - if (promotedType == QMetaType::UInt) - return uint(l1) == uint(l2); - if (promotedType == QMetaType::LongLong) - return l1 == l2; - if (promotedType == QMetaType::ULongLong) - return qulonglong(l1) == qulonglong(l2); - - Q_UNREACHABLE(); - return 0; -} + if (lhs == rhs) + return QPartialOrdering::Equivalent; + if constexpr (std::numeric_limits<Numeric>::has_quiet_NaN) { + if (std::isnan(lhs) || std::isnan(rhs)) + return QPartialOrdering::Unordered; + } -namespace { -template<typename Numeric> -int spaceShip(Numeric lhs, Numeric rhs) -{ bool smaller; if constexpr (std::is_same_v<Numeric, QObject *>) smaller = std::less<QObject *>()(lhs, rhs); // can't use less all the time because of bool else smaller = lhs < rhs; - if (smaller) - return -1; - else if (lhs == rhs) - return 0; - else - return 1; -} + return smaller ? QPartialOrdering::Less : QPartialOrdering::Greater; } -static std::optional<int> integralCompare(uint promotedType, const QVariant::Private *d1, const QVariant::Private *d2) +static QPartialOrdering integralCompare(uint promotedType, const QVariant::Private *d1, const QVariant::Private *d2) { // use toLongLong to retrieve the data, it gets us all the bits - bool ok; - qlonglong l1 = qConvertToNumber(d1, &ok, promotedType == QMetaType::Bool); - if (!ok) - return std::nullopt; - - qlonglong l2 = qConvertToNumber(d2, &ok, promotedType == QMetaType::Bool); - if (!ok) - return std::nullopt; - - if (promotedType == QMetaType::Bool) - return spaceShip<bool>(l1, l2); - if (promotedType == QMetaType::Int) - return spaceShip<int>(l1, l2); + std::optional<qlonglong> l1 = qConvertToNumber(d1, promotedType == QMetaType::Bool); + std::optional<qlonglong> l2 = qConvertToNumber(d2, promotedType == QMetaType::Bool); + if (!l1 || !l2) + return QPartialOrdering::Unordered; if (promotedType == QMetaType::UInt) - return spaceShip<uint>(l1, l2); + return spaceShip<uint>(*l1, *l2); if (promotedType == QMetaType::LongLong) - return spaceShip<qlonglong>(l1, l2); + return spaceShip<qlonglong>(*l1, *l2); if (promotedType == QMetaType::ULongLong) - return spaceShip<qulonglong>(l1, l2); + return spaceShip<qulonglong>(*l1, *l2); - Q_UNREACHABLE(); - return 0; + return spaceShip<int>(*l1, *l2); } -static std::optional<int> numericCompare(const QVariant::Private *d1, const QVariant::Private *d2) +static QPartialOrdering numericCompare(const QVariant::Private *d1, const QVariant::Private *d2) { - uint promotedType = numericTypePromotion(d1->type().id(), d2->type().id()); + uint promotedType = numericTypePromotion(d1->typeInterface(), d2->typeInterface()); if (promotedType != QMetaType::QReal) return integralCompare(promotedType, d1, d2); - // qreal comparisons - bool ok; - qreal r1 = qConvertToRealNumber(d1, &ok); - if (!ok) - return std::nullopt; - qreal r2 = qConvertToRealNumber(d2, &ok); - if (!ok) - return std::nullopt; - if (r1 == r2) - return 0; - if (std::isnan(r1) || std::isnan(r2)) - return std::nullopt; - return spaceShip<qreal>(r1, r2); -} - -static bool numericEquals(const QVariant::Private *d1, const QVariant::Private *d2) -{ - uint promotedType = numericTypePromotion(d1->type().id(), d2->type().id()); - if (promotedType != QMetaType::QReal) - return integralEquals(promotedType, d1, d2); - - // qreal comparisons - bool ok; - qreal r1 = qConvertToRealNumber(d1, &ok); - if (!ok) - return false; - qreal r2 = qConvertToRealNumber(d2, &ok); - if (!ok) - return false; - if (r1 == r2) - return true; + // floating point comparison + const auto r1 = qConvertToRealNumber(d1); + const auto r2 = qConvertToRealNumber(d2); + if (!r1 || !r2) + return QPartialOrdering::Unordered; + if (*r1 == *r2) + return QPartialOrdering::Equivalent; - return false; + return spaceShip(*r1, *r2); } #ifndef QT_BOOTSTRAPPED -static bool canConvertMetaObject(QMetaType fromType, QMetaType toType) +static bool qvCanConvertMetaObject(QMetaType fromType, QMetaType toType) { if ((fromType.flags() & QMetaType::PointerToQObject) && (toType.flags() & QMetaType::PointerToQObject)) { @@ -2341,13 +2384,7 @@ static bool canConvertMetaObject(QMetaType fromType, QMetaType toType) return false; } -static bool pointerEquals(const QVariant::Private *d1, const QVariant::Private *d2) -{ - // simply check whether both types point to the same data - return d1->get<QObject *>() == d2->get<QObject *>(); -} - -static int pointerCompare(const QVariant::Private *d1, const QVariant::Private *d2) +static QPartialOrdering pointerCompare(const QVariant::Private *d1, const QVariant::Private *d2) { return spaceShip<QObject *>(d1->get<QObject *>(), d2->get<QObject *>()); } @@ -2362,12 +2399,12 @@ bool QVariant::equals(const QVariant &v) const if (metatype != v.metaType()) { // try numeric comparisons, with C++ type promotion rules (no conversion) - if (qIsNumericType(metatype.id()) && qIsNumericType(v.d.type().id())) - return numericEquals(&d, &v.d); + if (canBeNumericallyCompared(metatype.iface(), v.d.type().iface())) + return numericCompare(&d, &v.d) == QPartialOrdering::Equivalent; #ifndef QT_BOOTSTRAPPED // if both types are related pointers to QObjects, check if they point to the same object - if (canConvertMetaObject(metatype, v.metaType())) - return pointerEquals(&d, &v.d); + if (qvCanConvertMetaObject(metatype, v.metaType())) + return pointerCompare(&d, &v.d) == QPartialOrdering::Equivalent; #endif return false; } @@ -2379,18 +2416,6 @@ bool QVariant::equals(const QVariant &v) const return metatype.equals(d.storage(), v.d.storage()); } -static QPartialOrdering convertOptionalToPartialOrdering(const std::optional<int> &opt) -{ - if (!opt) - return QPartialOrdering::Unordered; - else if (*opt < 0) - return QPartialOrdering::Less; - else if (*opt == 0) - return QPartialOrdering::Equivalent; - else - return QPartialOrdering::Greater; -} - /*! Compares the objects at \a lhs and \a rhs for ordering. @@ -2418,11 +2443,11 @@ QPartialOrdering QVariant::compare(const QVariant &lhs, const QVariant &rhs) QMetaType t = lhs.d.type(); if (t != rhs.d.type()) { // try numeric comparisons, with C++ type promotion rules (no conversion) - if (qIsNumericType(lhs.d.type().id()) && qIsNumericType(rhs.d.type().id())) - return convertOptionalToPartialOrdering(numericCompare(&lhs.d, &rhs.d)); + if (canBeNumericallyCompared(lhs.d.type().iface(), rhs.d.type().iface())) + return numericCompare(&lhs.d, &rhs.d); #ifndef QT_BOOTSTRAPPED - if (canConvertMetaObject(lhs.metaType(), rhs.metaType())) - return convertOptionalToPartialOrdering(pointerCompare(&lhs.d, &rhs.d)); + if (qvCanConvertMetaObject(lhs.metaType(), rhs.metaType())) + return pointerCompare(&lhs.d, &rhs.d); #endif return QPartialOrdering::Unordered; } @@ -2436,7 +2461,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 */ /*! @@ -2446,7 +2471,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() { @@ -2457,6 +2482,42 @@ void *QVariant::data() } /*! + \since 6.6 + \fn template <typename T> const T* QVariant::get_if(const QVariant *v) + \fn template <typename T> 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() +*/ + +/*! + \since 6.6 + \fn template <typename T> T &QVariant::get(QVariant &v) + \fn template <typename T> const T &QVariant::get(const QVariant &v) + \fn template <typename T> T &&QVariant::get(QVariant &&v) + \fn template <typename T> const T &&QVariant::get(const QVariant &&v) + + If \a v contains an object of type \c T, returns a reference to the contained + object, otherwise the call has undefined behavior. + + The overloads taking a mutable \a v detach \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 get_if(), data() +*/ + +/*! Returns \c true if this is a null variant, false otherwise. A variant is considered null if it contains no initialized value or a null pointer. @@ -2494,6 +2555,22 @@ QDebug QVariant::qdebugHelper(QDebug dbg) const return dbg; } +QVariant QVariant::moveConstruct(QMetaType type, void *data) +{ + QVariant var; + var.d = QVariant::Private(type.d_ptr); + customConstruct<ForceMove, NonNull>(type.d_ptr, &var.d, data); + return var; +} + +QVariant QVariant::copyConstruct(QMetaType type, const void *data) +{ + QVariant var; + var.d = QVariant::Private(type.d_ptr); + customConstruct<UseCopy, NonNull>(type.d_ptr, &var.d, data); + return var; +} + #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED @@ -2513,7 +2590,7 @@ QT_WARNING_POP #endif -/*! \fn template<typename T> void QVariant::setValue(T &&value) +/*! \fn template<typename T, typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, QVariant>>> void QVariant::setValue(T &&value) Stores a copy of \a value. If \c{T} is a type that QVariant doesn't support, QMetaType is used to store the value. A compile @@ -2526,19 +2603,19 @@ QT_WARNING_POP \sa value(), fromValue(), canConvert() */ -/*! \fn template<typename T> void QVariant::setValue(const QVariant &value) +/*! \fn void QVariant::setValue(const QVariant &value) Copies \a value over this QVariant. It is equivalent to simply assigning \a value to this QVariant. */ -/*! \fn template<typename T> void QVariant::setValue(QVariant &&value) +/*! \fn void QVariant::setValue(QVariant &&value) Moves \a value over this QVariant. It is equivalent to simply move assigning \a value to this QVariant. */ -/*! \fn template<typename T> T QVariant::value() const +/*! \fn template<typename T> T QVariant::value() const & Returns the stored value converted to the template type \c{T}. Call canConvert() to find out whether a type can be converted. @@ -2578,7 +2655,7 @@ QT_WARNING_POP \sa canView(), Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE() */ -/*! \fn bool QVariant::canConvert() const +/*! \fn template<typename T> bool QVariant::canConvert() const Returns \c true if the variant can be converted to the template type \c{T}, otherwise false. @@ -2594,7 +2671,7 @@ QT_WARNING_POP \sa convert() */ -/*! \fn bool QVariant::canView() const +/*! \fn template<typename T> bool QVariant::canView() const Returns \c true if a mutable view of the template type \c{T} can be created on this variant, otherwise \c false. @@ -2611,12 +2688,15 @@ QT_WARNING_POP \snippet code/src_corelib_kernel_qvariant.cpp 7 - \note If you are working with custom types, you should use - the Q_DECLARE_METATYPE() macro to register your custom type. - \sa setValue(), value() */ +/*! \fn template<typename T, QVariant::if_rvalue<T> = true> static QVariant QVariant::fromValue(T &&value) + + \since 6.6 + \overload +*/ + /*! \fn template<typename... Types> QVariant QVariant::fromStdVariant(const std::variant<Types...> &value) \since 5.11 @@ -2631,6 +2711,47 @@ QT_WARNING_POP */ /*! + \fn template<typename... Types> QVariant QVariant::fromStdVariant(std::variant<Types...> &&value) + \since 6.6 + \overload +*/ + + +/*! + \since 6.7 + + Creates a variant of type \a type, and initializes it with + a copy of \c{*copy} if \a copy is not \nullptr (in which case, \a copy + must point to an object of type \a type). + + Note that you have to pass the address of the object you want stored. + + Usually, you never have to use this constructor, use QVariant::fromValue() + instead to construct variants from the pointer types represented by + \c QMetaType::VoidStar, and \c QMetaType::QObjectStar. + + If \a type does not support copy construction and \a copy is not \nullptr, + the variant will be invalid. Similarly, if \a copy is \nullptr and + \a type does not support default construction, the variant will be + invalid. + + Returns the QVariant created as described above. + + \sa QVariant::fromValue(), QMetaType::Type +*/ +QVariant QVariant::fromMetaType(QMetaType type, const void *copy) +{ + QVariant result; + type.registerType(); + const auto iface = type.iface(); + if (isValidMetaTypeForVariant(iface, copy)) { + result.d = Private(iface); + customConstruct(iface, &result.d, copy); + } + return result; +} + +/*! \fn template<typename T> T qvariant_cast(const QVariant &value) \relates QVariant @@ -2641,6 +2762,14 @@ QT_WARNING_POP \sa QVariant::value() */ +/*! + \fn template<typename T> T QVariant::qvariant_cast(QVariant &&value) + \overload + \since 6.7 + + Returns the given \a value converted to the template type \c{T}. +*/ + /*! \fn template<typename T> T qVariantValue(const QVariant &value) \relates QVariant \deprecated |