summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qvariant.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/kernel/qvariant.cpp')
-rw-r--r--src/corelib/kernel/qvariant.cpp785
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