summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2023-03-23 10:12:10 +0100
committerFabian Kosmale <fabian.kosmale@qt.io>2023-06-02 09:13:08 +0200
commitf564e905c1c258691603694aaa70303b029e4598 (patch)
treeed4e6da24fa75c6df7bf400e01535cdde87b506d
parent18a2c62c073371ab24685d0f98b4b0cc04d11a7e (diff)
QVariant: Support emplace
[ChangeLog][QtCore][QVariant] Implemented in-place construction for QVariant. The constructor taking std::in_place_type<Type> constructs an object of type Type directly inside QVariant's storage, without any further copy or move operations. QVariant::emplace() does the same when replacing the content of an existing QVariant and tries to reuse previously-allocated memory. Fixes: QTBUG-112187 Change-Id: I16614ad701fa3bb583976ed2001bb312f119a51f Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
-rw-r--r--src/corelib/kernel/qvariant.cpp63
-rw-r--r--src/corelib/kernel/qvariant.h39
-rw-r--r--tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp98
3 files changed, 200 insertions, 0 deletions
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp
index 882abdf5b4..b5783be83f 100644
--- a/src/corelib/kernel/qvariant.cpp
+++ b/src/corelib/kernel/qvariant.cpp
@@ -545,6 +545,28 @@ QVariant::QVariant(const QVariant &p)
non-initializer list \c{in_place_type_t} overload.
*/
+
+/*!
+ \fn template <typename Type, typename... Args, if_constructible<Type, Args...> = true> QVariant::emplace(Args&&... args)
+
+ \since 6.6
+ Replaces the object currently held in \c{*this} with an object of
+ type \c{Type}, 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 Type, typename List, typename... Args, if_constructible<Type, std::initializer_list<List> &, Args...> = true> QVariant::emplace(std::initializer_list<List> 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
@@ -556,6 +578,47 @@ QVariant::QVariant(std::in_place_t, QMetaType type) : d(type.iface())
}
/*!
+ \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.
diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h
index 9ed6fedd3c..00e2bb0072 100644
--- a/src/corelib/kernel/qvariant.h
+++ b/src/corelib/kernel/qvariant.h
@@ -444,6 +444,43 @@ public:
{ return d.storage(); }
inline const void *data() const { return constData(); }
+private:
+ template <typename Type>
+ void verifySuitableForEmplace()
+ {
+ static_assert(!std::is_reference_v<Type>,
+ "QVariant does not support reference types");
+ static_assert(!std::is_const_v<Type>,
+ "QVariant does not support const types");
+ static_assert(std::is_copy_constructible_v<Type>,
+ "QVariant requires that the type is copyable");
+ static_assert(std::is_destructible_v<Type>,
+ "QVariant requires that the type is destructible");
+ }
+
+ template <typename Type, typename... Args>
+ Type &emplaceImpl(Args&&... args)
+ {
+ verifySuitableForEmplace<Type>();
+ auto data = static_cast<Type *>(prepareForEmplace(QMetaType::fromType<Type>()));
+ return *q20::construct_at(data, std::forward<Args>(args)...);
+ }
+
+public:
+ template <typename Type, typename... Args,
+ if_constructible<Type, Args...> = true>
+ Type &emplace(Args&&... args)
+ {
+ return emplaceImpl<Type>(std::forward<Args>(args)...);
+ }
+
+ template <typename Type, typename List, typename... Args,
+ if_constructible<Type, std::initializer_list<List> &, Args...> = true>
+ Type &emplace(std::initializer_list<List> list, Args&&... args)
+ {
+ return emplaceImpl<Type>(list, std::forward<Args>(args)...);
+ }
+
template<typename T, typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, QVariant>>>
void setValue(T &&avalue)
{
@@ -580,6 +617,8 @@ private:
// used to setup the QVariant internals for the "real" inplace ctor
QVariant(std::in_place_t, QMetaType type);
+ // helper for emplace
+ void *prepareForEmplace(QMetaType type);
// These constructors don't create QVariants of the type associated
// with the enum, as expected, but they would create a QVariant of
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
index 4fa42a1d52..feb03a029f 100644
--- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
+++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
@@ -380,6 +380,7 @@ private slots:
void copyNonDefaultConstructible();
void inplaceConstruct();
+ void emplace();
void getIf_int() { getIf_impl(42); }
void getIf_QString() { getIf_impl(u"string"_s); };
@@ -5767,6 +5768,103 @@ void tst_QVariant::inplaceConstruct()
}
}
+struct LargerThanInternalQVariantStorage {
+ char data[6 * sizeof(void *)];
+};
+
+struct alignas(256) LargerThanInternalQVariantStorageOveraligned {
+ char data[6 * sizeof(void *)];
+};
+
+struct alignas(128) SmallerAlignmentEvenLargerSize {
+ char data[17 * sizeof(void *)];
+};
+
+void tst_QVariant::emplace()
+{
+ {
+ // can emplace non default constructible + can emplace on null variant
+ NonDefaultConstructible ndc(42);
+ QVariant var;
+ var.emplace<NonDefaultConstructible>(42);
+ QVERIFY(get_if<NonDefaultConstructible>(&var));
+ QCOMPARE(get<NonDefaultConstructible>(var), ndc);
+ }
+ {
+ // can emplace using ctor taking initializer_list
+ QVariant var;
+ var.emplace<std::vector<int>>({0, 1, 2, 3, 4});
+ auto vecPtr = get_if<std::vector<int>>(&var);
+ QVERIFY(vecPtr);
+ QCOMPARE(vecPtr->size(), 5U);
+ for (int i = 0; i < 5; ++i)
+ QCOMPARE(vecPtr->at(size_t(i)), i);
+ }
+ // prequisites for the test
+ QCOMPARE_LE(sizeof(std::vector<int>), sizeof(std::string));
+ QCOMPARE(alignof(std::vector<int>), alignof(std::string));
+ {
+ // emplace can reuse storage
+ auto var = QVariant::fromValue(std::string{});
+ QVERIFY(var.data_ptr().is_shared);
+ auto data = var.constData();
+ std::vector<int> &vec = var.emplace<std::vector<int>>(3, 42);
+ /* alignment is the same, so the pointer is exactly the same;
+ no offset change */
+ auto expected = std::vector<int>{42, 42, 42};
+ QCOMPARE(get_if<std::vector<int>>(&var), &vec);
+ QCOMPARE(get<std::vector<int>>(var), expected);
+ QCOMPARE(var.constData(), data);
+ }
+ {
+ // emplace can't reuse storage if the variant is shared
+ auto var = QVariant::fromValue(std::string{});
+ [[maybe_unused]] QVariant causesSharing = var;
+ QVERIFY(var.data_ptr().is_shared);
+ auto data = var.constData();
+ var.emplace<std::vector<int>>(3, 42);
+ auto expected = std::vector<int>{42, 42, 42};
+ QVERIFY(get_if<std::vector<int>>(&var));
+ QCOMPARE(get<std::vector<int>>(var), expected);
+ QCOMPARE_NE(var.constData(), data);
+ }
+ {
+ // emplace puts element into the correct place - non-shared
+ QVERIFY(QVariant::Private::canUseInternalSpace(QMetaType::fromType<QString>().iface()));
+ QVariant var;
+ var.emplace<QString>(QChar('x'));
+ QVERIFY(!var.data_ptr().is_shared);
+ }
+ {
+ // emplace puts element into the correct place - shared
+ QVERIFY(!QVariant::Private::canUseInternalSpace(QMetaType::fromType<std::string>().iface()));
+ QVariant var;
+ var.emplace<std::string>(42, 'x');
+ QVERIFY(var.data_ptr().is_shared);
+ }
+ {
+ // emplace does not reuse the storage if alignment is too large
+ auto iface = QMetaType::fromType<LargerThanInternalQVariantStorage>().iface();
+ QVERIFY(!QVariant::Private::canUseInternalSpace(iface));
+ auto var = QVariant::fromValue(LargerThanInternalQVariantStorage{});
+ auto data = var.constData();
+ var.emplace<LargerThanInternalQVariantStorageOveraligned>();
+ QCOMPARE_NE(var.constData(), data);
+ }
+ {
+ // emplace does reuse the storage if new alignment and size are together small enough
+ auto iface = QMetaType::fromType<LargerThanInternalQVariantStorageOveraligned>().iface();
+ QVERIFY(!QVariant::Private::canUseInternalSpace(iface));
+ auto var = QVariant::fromValue(LargerThanInternalQVariantStorageOveraligned{});
+ auto data = var.constData();
+ var.emplace<SmallerAlignmentEvenLargerSize>();
+ // no exact match below - the alignment is after all different
+ QCOMPARE_LE(quintptr(var.constData()), quintptr(data));
+ QCOMPARE_LE(quintptr(var.constData()),
+ quintptr(data) + sizeof(LargerThanInternalQVariantStorageOveraligned));
+ }
+}
+
void tst_QVariant::getIf_NonDefaultConstructible()
{
getIf_impl(NonDefaultConstructible{42});