diff --git a/src/corelib/kernel/qmetatype_p.h b/src/corelib/kernel/qmetatype_p.h index e649394832..71a225d5e6 100644 --- a/src/corelib/kernel/qmetatype_p.h +++ b/src/corelib/kernel/qmetatype_p.h @@ -178,6 +178,15 @@ inline void copyConstruct(const QtPrivate::QMetaTypeInterface *iface, void *wher memcpy(where, copy, iface->size); } +inline void moveConstruct(const QtPrivate::QMetaTypeInterface *iface, void *where, void *copy) +{ + Q_ASSERT(isMoveConstructible(iface)); + if (iface->moveCtr) + iface->moveCtr(iface, where, copy); + else + memcpy(where, copy, iface->size); +} + inline void construct(const QtPrivate::QMetaTypeInterface *iface, void *where, const void *copy) { if (copy) diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index ebb99d3180..8e2457350c 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -47,6 +47,8 @@ #include "qline.h" #endif +#include + #include #include #include @@ -231,9 +233,38 @@ static bool isValidMetaTypeForVariant(const QtPrivate::QMetaTypeInterface *iface return true; } +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) +}; + +enum CustomConstructNullabilityOption { + MaybeNull, // copy might be null, might be non-null + NonNull, // copy is guarantueed to be non-null + // future option: AlwaysNull? +}; + + +template static QVariant::PrivateShared * +customConstructShared(size_t size, size_t align, F &&construct) +{ + struct Deleter { + void operator()(QVariant::PrivateShared *p) const + { QVariant::PrivateShared::free(p); } + }; + + // this is exception-safe + std::unique_ptr ptr; + ptr.reset(QVariant::PrivateShared::create(size, align)); + construct(ptr->data()); + return ptr.release(); +} + // the type of d has already been set, but other field are not set +template static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant::Private *d, - const void *copy) + std::conditional_t copy) { using namespace QtMetaTypePrivate; Q_ASSERT(iface); @@ -242,6 +273,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_ASSUME(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. @@ -251,11 +286,17 @@ static void customConstruct(const QtPrivate::QMetaTypeInterface *iface, QVariant if (QVariant::Private::canUseInternalSpace(iface)) { d->is_shared = false; if (!copy && !iface->defaultCtr) - return; // default constructor and it's OK to build in 0-filled storage, which we've already done - 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; } @@ -1064,7 +1105,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(d.typeInterface(), &dd, constData()); if (!d.data.shared->ref.deref()) customClear(&d); d.data.shared = dd.data.shared; @@ -2524,6 +2566,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(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(type.d_ptr, &var.d, data); + return var; +} + #if QT_DEPRECATED_SINCE(6, 0) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED @@ -2644,6 +2702,12 @@ QT_WARNING_POP \sa setValue(), value() */ +/*! \fn template static QVariant QVariant::fromValue(T &&value) + + \since 6.6 + \overload +*/ + /*! \fn template QVariant QVariant::fromStdVariant(const std::variant &value) \since 5.11 diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index b1865e442b..582f135fab 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -71,6 +71,9 @@ class Q_CORE_EXPORT QVariant >, bool>; + template + using if_rvalue = std::enable_if_t, bool>; + struct CborValueStandIn { qint64 n; void *c; int t; }; public: struct PrivateShared @@ -516,6 +519,39 @@ public: return t; } + template = true> +#ifndef Q_QDOC + /* needs is_copy_constructible for variants semantics, is_move_constructible so that moveConstruct works + (but copy_constructible implies move_constructble, so don't bother checking) + */ + static inline auto fromValue(T &&value) + noexcept(std::is_nothrow_copy_constructible_v && Private::CanUseInternalSpace) + -> std::enable_if_t, + std::is_destructible>, QVariant> +#else + static inline QVariant fromValue(T &&value) +#endif + { + // handle special cases + using Type = std::remove_cv_t; + if constexpr (std::is_null_pointer_v) + return QVariant(QMetaType::fromType()); + else if constexpr (std::is_same_v) + return std::forward(value); + else if constexpr (std::is_same_v) + return QVariant(); + QMetaType mt = QMetaType::fromType(); + mt.registerType(); // we want the type stored in QVariant to always be registered + // T is a forwarding reference, so if T satifies the enable-ifery, + // we get this overload even if T is an lvalue reference and thus must check here + // Moreover, we only try to move if the type is actually moveable and not if T is const + // as in const int i; QVariant::fromValue(std::move(i)); + if constexpr (std::conjunction_v, std::negation>>) + return moveConstruct(QMetaType::fromType(), std::addressof(value)); + else + return copyConstruct(mt, std::addressof(value)); + } + template #ifndef Q_QDOC static inline auto fromValue(const T &value) @@ -594,6 +630,9 @@ private: Q_MK_GET(const &&) #undef Q_MK_GET + static QVariant moveConstruct(QMetaType type, void *data); + static QVariant copyConstruct(QMetaType type, const void *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 feb03a029f..addf7ceec9 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -5573,6 +5573,16 @@ void tst_QVariant::mutableView() QCOMPARE(extracted.text, nullptr); } +struct MoveTester +{ + bool wasMoved = false; + MoveTester() = default; + MoveTester(const MoveTester &) {}; // non-trivial on purpose + MoveTester(MoveTester &&other) { other.wasMoved = true; } + MoveTester& operator=(const MoveTester &) = default; + MoveTester& operator=(MoveTester &&other) {other.wasMoved = true; return *this;} +}; + void tst_QVariant::moveOperations() { { @@ -5594,6 +5604,19 @@ void tst_QVariant::moveOperations() v = QVariant::fromValue(list); v2 = std::move(v); QVERIFY(v2.value>() == list); + + { + MoveTester tester; + QVariant::fromValue(tester); + QVERIFY(!tester.wasMoved); + QVariant::fromValue(std::move(tester)); + QVERIFY(tester.wasMoved); + } + { + const MoveTester tester; + QVariant::fromValue(std::move(tester)); + QVERIFY(!tester.wasMoved); // we don't want to move from const variables + } } class NoMetaObject : public QObject {};