Revision control

Copy as Markdown

// Copyright (C) 2020 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#ifndef QARRAYDATAOPS_H
#define QARRAYDATAOPS_H
#include <QtCore/qarraydata.h>
#include <QtCore/qcontainertools_impl.h>
#include <QtCore/qnamespace.h>
#include <QtCore/q20functional.h>
#include <QtCore/q20memory.h>
#include <new>
#include <string.h>
#include <utility>
#include <iterator>
#include <type_traits>
QT_BEGIN_NAMESPACE
template <class T> struct QArrayDataPointer;
namespace QtPrivate {
template <class T>
struct QPodArrayOps
{
static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");
protected:
typedef QTypedArrayData<T> Data;
using DataPointer = QArrayDataPointer<T>;
private:
DataPointer *m_ptr;
public:
explicit QPodArrayOps(DataPointer &dp) : m_ptr{&dp} {}
DataPointer *that()
{ return m_ptr; }
const DataPointer *that() const
{ return m_ptr; }
typedef typename QArrayDataPointer<T>::parameter_type parameter_type;
void copyAppend(const T *b, const T *e) noexcept
{
Q_ASSERT(that()->isMutable() || b == e);
Q_ASSERT(!that()->isShared() || b == e);
Q_ASSERT(b <= e);
Q_ASSERT((e - b) <= that()->freeSpaceAtEnd());
if (b == e)
return;
::memcpy(static_cast<void *>(that()->end()), static_cast<const void *>(b), (e - b) * sizeof(T));
that()->size += (e - b);
}
void copyAppend(qsizetype n, parameter_type t) noexcept
{
Q_ASSERT(!that()->isShared() || n == 0);
Q_ASSERT(that()->freeSpaceAtEnd() >= n);
if (!n)
return;
T *where = that()->end();
that()->size += qsizetype(n);
while (n--)
*where++ = t;
}
void moveAppend(T *b, T *e) noexcept
{
copyAppend(b, e);
}
void truncate(size_t newSize) noexcept
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(!that()->isShared());
Q_ASSERT(newSize <= size_t(that()->size));
that()->size = qsizetype(newSize);
}
void destroyAll() noexcept // Call from destructors, ONLY!
{
Q_ASSERT(that()->d);
Q_ASSERT(that()->d->ref_.loadRelaxed() == 0);
// As this is to be called only from destructor, it doesn't need to be
// exception safe; size not updated.
}
T *createHole(QArrayData::GrowthPosition pos, qsizetype where, qsizetype n)
{
Q_ASSERT((pos == QArrayData::GrowsAtBeginning && n <= that()->freeSpaceAtBegin()) ||
(pos == QArrayData::GrowsAtEnd && n <= that()->freeSpaceAtEnd()));
T *insertionPoint = that()->ptr + where;
if (pos == QArrayData::GrowsAtEnd) {
if (where < that()->size)
::memmove(static_cast<void *>(insertionPoint + n), static_cast<void *>(insertionPoint), (that()->size - where) * sizeof(T));
} else {
Q_ASSERT(where == 0);
that()->ptr -= n;
insertionPoint -= n;
}
that()->size += n;
return insertionPoint;
}
void insert(qsizetype i, const T *data, qsizetype n)
{
typename Data::GrowthPosition pos = Data::GrowsAtEnd;
if (that()->size != 0 && i == 0)
pos = Data::GrowsAtBeginning;
DataPointer oldData;
that()->detachAndGrow(pos, n, &data, &oldData);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
T *where = createHole(pos, i, n);
::memcpy(static_cast<void *>(where), static_cast<const void *>(data), n * sizeof(T));
}
void insert(qsizetype i, qsizetype n, parameter_type t)
{
T copy(t);
typename Data::GrowthPosition pos = Data::GrowsAtEnd;
if (that()->size != 0 && i == 0)
pos = Data::GrowsAtBeginning;
that()->detachAndGrow(pos, n, nullptr, nullptr);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
T *where = createHole(pos, i, n);
while (n--)
*where++ = copy;
}
template<typename... Args>
void emplace(qsizetype i, Args &&... args)
{
bool detach = that()->needsDetach();
if (!detach) {
if (i == that()->size && that()->freeSpaceAtEnd()) {
new (that()->end()) T(std::forward<Args>(args)...);
++that()->size;
return;
}
if (i == 0 && that()->freeSpaceAtBegin()) {
new (that()->begin() - 1) T(std::forward<Args>(args)...);
--that()->ptr;
++that()->size;
return;
}
}
T tmp(std::forward<Args>(args)...);
typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd;
if (that()->size != 0 && i == 0)
pos = QArrayData::GrowsAtBeginning;
that()->detachAndGrow(pos, 1, nullptr, nullptr);
T *where = createHole(pos, i, 1);
new (where) T(std::move(tmp));
}
void erase(T *b, qsizetype n)
{
T *e = b + n;
Q_ASSERT(that()->isMutable());
Q_ASSERT(b < e);
Q_ASSERT(b >= that()->begin() && b < that()->end());
Q_ASSERT(e > that()->begin() && e <= that()->end());
// Comply with std::vector::erase(): erased elements and all after them
// are invalidated. However, erasing from the beginning effectively
// means that all iterators are invalidated. We can use this freedom to
// erase by moving towards the end.
if (b == that()->begin() && e != that()->end()) {
that()->ptr = e;
} else if (e != that()->end()) {
::memmove(static_cast<void *>(b), static_cast<void *>(e),
(static_cast<T *>(that()->end()) - e) * sizeof(T));
}
that()->size -= n;
}
void eraseFirst() noexcept
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(that()->size);
++that()->ptr;
--that()->size;
}
void eraseLast() noexcept
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(that()->size);
--that()->size;
}
template <typename Predicate>
qsizetype eraseIf(Predicate pred)
{
qsizetype result = 0;
if (that()->size == 0)
return result;
if (!that()->needsDetach()) {
auto end = that()->end();
auto it = std::remove_if(that()->begin(), end, pred);
if (it != end) {
result = std::distance(it, end);
erase(it, result);
}
} else {
const auto begin = that()->begin();
const auto end = that()->end();
auto it = std::find_if(begin, end, pred);
if (it == end)
return result;
QArrayDataPointer<T> other(that()->size);
Q_CHECK_PTR(other.data());
auto dest = other.begin();
// std::uninitialized_copy will fallback to ::memcpy/memmove()
dest = std::uninitialized_copy(begin, it, dest);
dest = q_uninitialized_remove_copy_if(std::next(it), end, dest, pred);
other.size = std::distance(other.data(), dest);
result = that()->size - other.size;
that()->swap(other);
}
return result;
}
struct Span { T *begin; T *end; };
void copyRanges(std::initializer_list<Span> ranges)
{
auto it = that()->begin();
std::for_each(ranges.begin(), ranges.end(), [&it](const auto &span) {
it = std::copy(span.begin, span.end, it);
});
that()->size = std::distance(that()->begin(), it);
}
void assign(T *b, T *e, parameter_type t) noexcept
{
Q_ASSERT(b <= e);
Q_ASSERT(b >= that()->begin() && e <= that()->end());
while (b != e)
::memcpy(static_cast<void *>(b++), static_cast<const void *>(&t), sizeof(T));
}
void reallocate(qsizetype alloc, QArrayData::AllocationOption option)
{
auto pair = Data::reallocateUnaligned(that()->d, that()->ptr, alloc, option);
Q_CHECK_PTR(pair.ptr);
Q_ASSERT(pair.header != nullptr);
that()->d = pair.header;
that()->ptr = pair.ptr;
}
};
template <class T>
struct QGenericArrayOps
{
static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");
protected:
typedef QTypedArrayData<T> Data;
using DataPointer = QArrayDataPointer<T>;
private:
DataPointer *m_ptr;
public:
explicit QGenericArrayOps(DataPointer &dp) : m_ptr{&dp} {}
DataPointer *that()
{ return m_ptr; }
const DataPointer *that() const
{ return m_ptr; }
typedef typename QArrayDataPointer<T>::parameter_type parameter_type;
void copyAppend(const T *b, const T *e)
{
Q_ASSERT(that()->isMutable() || b == e);
Q_ASSERT(!that()->isShared() || b == e);
Q_ASSERT(b <= e);
Q_ASSERT((e - b) <= that()->freeSpaceAtEnd());
if (b == e) // short-cut and handling the case b and e == nullptr
return;
T *data = that()->begin();
while (b < e) {
new (data + that()->size) T(*b);
++b;
++that()->size;
}
}
void copyAppend(qsizetype n, parameter_type t)
{
Q_ASSERT(!that()->isShared() || n == 0);
Q_ASSERT(that()->freeSpaceAtEnd() >= n);
if (!n)
return;
T *data = that()->begin();
while (n--) {
new (data + that()->size) T(t);
++that()->size;
}
}
void moveAppend(T *b, T *e)
{
Q_ASSERT(that()->isMutable() || b == e);
Q_ASSERT(!that()->isShared() || b == e);
Q_ASSERT(b <= e);
Q_ASSERT((e - b) <= that()->freeSpaceAtEnd());
if (b == e)
return;
T *data = that()->begin();
while (b < e) {
new (data + that()->size) T(std::move(*b));
++b;
++that()->size;
}
}
void truncate(size_t newSize)
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(!that()->isShared());
Q_ASSERT(newSize <= size_t(that()->size));
std::destroy(that()->begin() + newSize, that()->end());
that()->size = newSize;
}
void destroyAll() // Call from destructors, ONLY
{
Q_ASSERT(that()->d);
// As this is to be called only from destructor, it doesn't need to be
// exception safe; size not updated.
Q_ASSERT(that()->d->ref_.loadRelaxed() == 0);
std::destroy(that()->begin(), that()->end());
}
struct Inserter
{
QArrayDataPointer<T> *data;
T *begin;
qsizetype size;
qsizetype sourceCopyConstruct = 0, nSource = 0, move = 0, sourceCopyAssign = 0;
T *end = nullptr, *last = nullptr, *where = nullptr;
Inserter(QArrayDataPointer<T> *d) : data(d)
{
begin = d->ptr;
size = d->size;
}
~Inserter() {
data->ptr = begin;
data->size = size;
}
Q_DISABLE_COPY(Inserter)
void setup(qsizetype pos, qsizetype n)
{
end = begin + size;
last = end - 1;
where = begin + pos;
qsizetype dist = size - pos;
sourceCopyConstruct = 0;
nSource = n;
move = n - dist; // smaller 0
sourceCopyAssign = n;
if (n > dist) {
sourceCopyConstruct = n - dist;
move = 0;
sourceCopyAssign -= sourceCopyConstruct;
}
}
void insert(qsizetype pos, const T *source, qsizetype n)
{
qsizetype oldSize = size;
Q_UNUSED(oldSize);
setup(pos, n);
// first create new elements at the end, by copying from elements
// to be inserted (if they extend past the current end of the array)
for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
new (end + i) T(source[nSource - sourceCopyConstruct + i]);
++size;
}
Q_ASSERT(size <= oldSize + n);
// now move construct new elements at the end from existing elements inside
// the array.
for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
new (end + i) T(std::move(*(end + i - nSource)));
++size;
}
// array has the new size now!
Q_ASSERT(size == oldSize + n);
// now move assign existing elements towards the end
for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - nSource]);
// finally copy the remaining elements from source over
for (qsizetype i = 0; i != sourceCopyAssign; ++i)
where[i] = source[i];
}
void insert(qsizetype pos, const T &t, qsizetype n)
{
const qsizetype oldSize = size;
Q_UNUSED(oldSize);
setup(pos, n);
// first create new elements at the end, by copying from elements
// to be inserted (if they extend past the current end of the array)
for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
new (end + i) T(t);
++size;
}
Q_ASSERT(size <= oldSize + n);
// now move construct new elements at the end from existing elements inside
// the array.
for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
new (end + i) T(std::move(*(end + i - nSource)));
++size;
}
// array has the new size now!
Q_ASSERT(size == oldSize + n);
// now move assign existing elements towards the end
for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - nSource]);
// finally copy the remaining elements from source over
for (qsizetype i = 0; i != sourceCopyAssign; ++i)
where[i] = t;
}
void insertOne(qsizetype pos, T &&t)
{
setup(pos, 1);
if (sourceCopyConstruct) {
Q_ASSERT(sourceCopyConstruct == 1);
new (end) T(std::move(t));
++size;
} else {
// create a new element at the end by move constructing one existing element
// inside the array.
new (end) T(std::move(*(end - 1)));
++size;
// now move assign existing elements towards the end
for (qsizetype i = 0; i != move; --i)
last[i] = std::move(last[i - 1]);
// and move the new item into place
*where = std::move(t);
}
}
};
void insert(qsizetype i, const T *data, qsizetype n)
{
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
DataPointer oldData;
that()->detachAndGrow(pos, n, &data, &oldData);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(that()->freeSpaceAtBegin() >= n);
while (n) {
--n;
new (that()->begin() - 1) T(data[n]);
--that()->ptr;
++that()->size;
}
} else {
Inserter{that()}.insert(i, data, n);
}
}
void insert(qsizetype i, qsizetype n, parameter_type t)
{
Q_DECL_UNINITIALIZED
T copy(t);
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
that()->detachAndGrow(pos, n, nullptr, nullptr);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(that()->freeSpaceAtBegin() >= n);
while (n--) {
new (that()->begin() - 1) T(copy);
--that()->ptr;
++that()->size;
}
} else {
Inserter{that()}.insert(i, copy, n);
}
}
template<typename... Args>
void emplace(qsizetype i, Args &&... args)
{
bool detach = that()->needsDetach();
if (!detach) {
if (i == that()->size && that()->freeSpaceAtEnd()) {
new (that()->end()) T(std::forward<Args>(args)...);
++that()->size;
return;
}
if (i == 0 && that()->freeSpaceAtBegin()) {
new (that()->begin() - 1) T(std::forward<Args>(args)...);
--that()->ptr;
++that()->size;
return;
}
}
Q_DECL_UNINITIALIZED
T tmp(std::forward<Args>(args)...);
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
that()->detachAndGrow(pos, 1, nullptr, nullptr);
if (growsAtBegin) {
Q_ASSERT(that()->freeSpaceAtBegin());
new (that()->begin() - 1) T(std::move(tmp));
--that()->ptr;
++that()->size;
} else {
Inserter{that()}.insertOne(i, std::move(tmp));
}
}
void erase(T *b, qsizetype n)
{
T *e = b + n;
Q_ASSERT(that()->isMutable());
Q_ASSERT(b < e);
Q_ASSERT(b >= that()->begin() && b < that()->end());
Q_ASSERT(e > that()->begin() && e <= that()->end());
// Comply with std::vector::erase(): erased elements and all after them
// are invalidated. However, erasing from the beginning effectively
// means that all iterators are invalidated. We can use this freedom to
// erase by moving towards the end.
if (b == that()->begin() && e != that()->end()) {
that()->ptr = e;
} else {
const T *const end = that()->end();
// move (by assignment) the elements from e to end
// onto b to the new end
while (e != end) {
*b = std::move(*e);
++b;
++e;
}
}
that()->size -= n;
std::destroy(b, e);
}
void eraseFirst() noexcept
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(that()->size);
that()->begin()->~T();
++that()->ptr;
--that()->size;
}
void eraseLast() noexcept
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(that()->size);
(that()->end() - 1)->~T();
--that()->size;
}
void assign(T *b, T *e, parameter_type t)
{
Q_ASSERT(b <= e);
Q_ASSERT(b >= that()->begin() && e <= that()->end());
while (b != e)
*b++ = t;
}
};
template <class T>
struct QMovableArrayOps
: QGenericArrayOps<T>
{
using Base = QGenericArrayOps<T>;
static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");
protected:
typedef QTypedArrayData<T> Data;
using DataPointer = QArrayDataPointer<T>;
public:
explicit QMovableArrayOps(DataPointer &dp) : Base(dp) {}
DataPointer *that()
{ return Base::that(); }
const DataPointer *that() const
{ return Base::that(); }
// using QGenericArrayOps<T>::copyAppend;
// using QGenericArrayOps<T>::moveAppend;
// using QGenericArrayOps<T>::truncate;
// using QGenericArrayOps<T>::destroyAll;
typedef typename QGenericArrayOps<T>::parameter_type parameter_type;
struct Inserter
{
QArrayDataPointer<T> * const data;
T *displaceFrom;
T * const displaceTo;
const qsizetype nInserts = 0;
const size_t bytes;
void verifyPost()
{ Q_ASSERT(displaceFrom == displaceTo); }
explicit Inserter(QArrayDataPointer<T> *d, qsizetype pos, qsizetype n)
: data{d},
displaceFrom{d->ptr + pos},
displaceTo{displaceFrom + n},
nInserts{n},
bytes{(data->size - pos) * sizeof(T)}
{
::memmove(static_cast<void *>(displaceTo), static_cast<void *>(displaceFrom), bytes);
}
~Inserter() {
auto inserts = nInserts;
if constexpr (!std::is_nothrow_copy_constructible_v<T>) {
if (displaceFrom != displaceTo) {
::memmove(static_cast<void *>(displaceFrom), static_cast<void *>(displaceTo), bytes);
inserts -= qAbs(displaceFrom - displaceTo);
}
}
data->size += inserts;
}
Q_DISABLE_COPY(Inserter)
void insertRange(const T *source, qsizetype n)
{
while (n--) {
new (displaceFrom) T(*source);
++source;
++displaceFrom;
}
verifyPost();
}
void insertFill(const T &t, qsizetype n)
{
while (n--) {
new (displaceFrom) T(t);
++displaceFrom;
}
verifyPost();
}
void insertOne(T &&t)
{
new (displaceFrom) T(std::move(t));
++displaceFrom;
verifyPost();
}
};
void insert(qsizetype i, const T *data, qsizetype n)
{
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
DataPointer oldData;
that()->detachAndGrow(pos, n, &data, &oldData);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(that()->freeSpaceAtBegin() >= n);
while (n) {
--n;
new (that()->begin() - 1) T(data[n]);
--that()->ptr;
++that()->size;
}
} else {
Inserter{that(), i, n}.insertRange(data, n);
}
}
void insert(qsizetype i, qsizetype n, parameter_type t)
{
Q_DECL_UNINITIALIZED
T copy(t);
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
that()->detachAndGrow(pos, n, nullptr, nullptr);
Q_ASSERT((pos == Data::GrowsAtBeginning && that()->freeSpaceAtBegin() >= n) ||
(pos == Data::GrowsAtEnd && that()->freeSpaceAtEnd() >= n));
if (growsAtBegin) {
// copy construct items in reverse order at the begin
Q_ASSERT(that()->freeSpaceAtBegin() >= n);
while (n--) {
new (that()->begin() - 1) T(copy);
--that()->ptr;
++that()->size;
}
} else {
Inserter{that(), i, n}.insertFill(copy, n);
}
}
template<typename... Args>
void emplace(qsizetype i, Args &&... args)
{
bool detach = that()->needsDetach();
if (!detach) {
if (i == that()->size && that()->freeSpaceAtEnd()) {
new (that()->end()) T(std::forward<Args>(args)...);
++that()->size;
return;
}
if (i == 0 && that()->freeSpaceAtBegin()) {
new (that()->begin() - 1) T(std::forward<Args>(args)...);
--that()->ptr;
++that()->size;
return;
}
}
Q_DECL_UNINITIALIZED
T tmp(std::forward<Args>(args)...);
const bool growsAtBegin = that()->size != 0 && i == 0;
const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;
that()->detachAndGrow(pos, 1, nullptr, nullptr);
if (growsAtBegin) {
Q_ASSERT(that()->freeSpaceAtBegin());
new (that()->begin() - 1) T(std::move(tmp));
--that()->ptr;
++that()->size;
} else {
Inserter{that(), i, 1}.insertOne(std::move(tmp));
}
}
void erase(T *b, qsizetype n)
{
T *e = b + n;
Q_ASSERT(that()->isMutable());
Q_ASSERT(b < e);
Q_ASSERT(b >= that()->begin() && b < that()->end());
Q_ASSERT(e > that()->begin() && e <= that()->end());
// Comply with std::vector::erase(): erased elements and all after them
// are invalidated. However, erasing from the beginning effectively
// means that all iterators are invalidated. We can use this freedom to
// erase by moving towards the end.
std::destroy(b, e);
if (b == that()->begin() && e != that()->end()) {
that()->ptr = e;
} else if (e != that()->end()) {
memmove(static_cast<void *>(b), static_cast<const void *>(e), (static_cast<const T *>(that()->end()) - e)*sizeof(T));
}
that()->size -= n;
}
void reallocate(qsizetype alloc, QArrayData::AllocationOption option)
{
auto pair = Data::reallocateUnaligned(that()->d, that()->ptr, alloc, option);
Q_CHECK_PTR(pair.ptr);
Q_ASSERT(pair.header != nullptr);
that()->d = pair.header;
that()->ptr = pair.ptr;
}
};
template <class T, class = void>
struct QArrayOpsSelector
{
typedef QGenericArrayOps<T> Type;
};
template <class T>
struct QArrayOpsSelector<T,
typename std::enable_if<
!QTypeInfo<T>::isComplex && QTypeInfo<T>::isRelocatable
>::type>
{
typedef QPodArrayOps<T> Type;
};
template <class T>
struct QArrayOpsSelector<T,
typename std::enable_if<
QTypeInfo<T>::isComplex && QTypeInfo<T>::isRelocatable
>::type>
{
typedef QMovableArrayOps<T> Type;
};
template <class T>
struct QCommonArrayOps : QArrayOpsSelector<T>::Type
{
using Base = typename QArrayOpsSelector<T>::Type;
using Data = QTypedArrayData<T>;
using DataPointer = QArrayDataPointer<T>;
using parameter_type = typename Base::parameter_type;
protected:
using Self = QCommonArrayOps<T>;
public:
using Base::Base;
DataPointer *that()
{ return Base::that(); }
const DataPointer *that() const
{ return Base::that(); }
// using Base::truncate;
// using Base::destroyAll;
template<typename It>
void appendIteratorRange(It b, It e, QtPrivate::IfIsForwardIterator<It> = true)
{
Q_ASSERT(that()->isMutable() || b == e);
Q_ASSERT(!that()->isShared() || b == e);
const qsizetype distance = std::distance(b, e);
Q_ASSERT(distance >= 0 && distance <= that()->allocatedCapacity() - that()->size);
Q_UNUSED(distance);
#if __cplusplus >= 202002L && defined(__cpp_concepts) && defined(__cpp_lib_concepts)
constexpr bool canUseCopyAppend =
std::contiguous_iterator<It> &&
std::is_same_v<
std::remove_cv_t<typename std::iterator_traits<It>::value_type>,
T
>;
if constexpr (canUseCopyAppend) {
Base::copyAppend(std::to_address(b), std::to_address(e));
} else
#endif
{
T *iter = that()->end();
for (; b != e; ++iter, ++b) {
new (iter) T(*b);
++that()->size;
}
}
}
// slightly higher level API than copyAppend() that also preallocates space
void growAppend(const T *b, const T *e)
{
if (b == e)
return;
Q_ASSERT(b < e);
const qsizetype n = e - b;
DataPointer old;
// points into range:
if (QtPrivate::q_points_into_range(b, *that()))
that()->detachAndGrow(QArrayData::GrowsAtEnd, n, &b, &old);
else
that()->detachAndGrow(QArrayData::GrowsAtEnd, n, nullptr, nullptr);
Q_ASSERT(that()->freeSpaceAtEnd() >= n);
// b might be updated so use [b, n)
Base::copyAppend(b, b + n);
}
void appendUninitialized(qsizetype newSize)
{
Q_ASSERT(that()->isMutable());
Q_ASSERT(!that()->isShared());
Q_ASSERT(newSize > that()->size);
Q_ASSERT(newSize - that()->size <= that()->freeSpaceAtEnd());
T *const b = that()->begin() + that()->size;
T *const e = that()->begin() + newSize;
if constexpr (std::is_constructible_v<T, Qt::Initialization>)
std::uninitialized_fill(b, e, Qt::Uninitialized);
else
std::uninitialized_default_construct(b, e);
that()->size = newSize;
}
using Base::assign;
template <typename InputIterator, typename Projection = q20::identity>
void assign(InputIterator first, InputIterator last, Projection proj = {})
{
// This function only provides the basic exception guarantee.
using Category = typename std::iterator_traits<InputIterator>::iterator_category;
constexpr bool IsFwdIt = std::is_convertible_v<Category, std::forward_iterator_tag>;
const qsizetype n = IsFwdIt ? std::distance(first, last) : 0;
bool undoPrependOptimization = true;
bool needCapacity = n > that()->constAllocatedCapacity();
if (needCapacity || that()->needsDetach()) {
qsizetype newCapacity = that()->detachCapacity(n);
bool wasLastRef = !that()->deref();
if (wasLastRef && needCapacity) {
// free memory we can't reuse
Base::destroyAll();
Data::deallocate(that()->d);
}
if (!needCapacity && wasLastRef) {
// we were the last reference and can reuse the storage
that()->d->ref_.storeRelaxed(1);
} else {
// we must allocate new memory
auto [hdr, p] = Data::allocate(newCapacity);
that()->d = hdr;
that()->ptr = p;
that()->size = 0;
undoPrependOptimization = false;
}
}
if constexpr (!std::is_nothrow_constructible_v<T, decltype(std::invoke(proj, *first))>
|| !std::is_nothrow_invocable_v<Projection, decltype(*first)>)
{
// If construction can throw, and we have freeSpaceAtBegin(),
// it's easiest to just clear the container and start fresh.
// The alternative would be to keep track of two active, disjoint ranges.
if (undoPrependOptimization) {
Base::truncate(0);
that()->setBegin(Data::dataStart(that()->d, alignof(typename Data::AlignmentDummy)));
undoPrependOptimization = false;
}
}
const auto dend = that()->end();
T *dst = that()->begin();
T *capacityBegin = dst;
if (undoPrependOptimization) {
capacityBegin = Data::dataStart(that()->d, alignof(typename Data::AlignmentDummy));
that()->setBegin(capacityBegin); // undo prepend optimization
}
assign_impl(first, last, capacityBegin, dst, dend, proj, Category{});
}
template <typename InputIterator, typename Projection>
void assign_impl(InputIterator first, InputIterator last, T *capacityBegin, T *dst, T *dend,
Projection proj, std::input_iterator_tag)
{
if (qsizetype offset = dst - capacityBegin) {
T *prependBufferEnd = dst;
dst = capacityBegin;
// By construction, the following loop is nothrow!
// (otherwise, we can't reach here)
// Assumes InputIterator operations don't throw.
// (but we can't statically assert that, as these operations
// have preconditons, so typically aren't noexcept)
while (true) {
if (dst == prependBufferEnd) { // ran out of prepend buffer space
that()->size += offset;
// we now have a contiguous buffer, continue with the main loop:
break;
}
if (first == last) { // ran out of elements to assign
std::destroy(prependBufferEnd, dend);
that()->size = dst - that()->begin();
return;
}
// construct element in prepend buffer
q20::construct_at(dst, std::invoke(proj, *first));
++dst;
++first;
}
}
while (true) {
if (first == last) { // ran out of elements to assign
std::destroy(dst, dend);
break;
}
if (dst == dend) { // ran out of existing elements to overwrite
do {
Base::emplace(that()->size, std::invoke(proj, *first));
} while (++first != last);
return; // size() is already correct (and dst invalidated)!
}
*dst = std::invoke(proj, *first); // overwrite existing element
++dst;
++first;
}
that()->size = dst - that()->begin();
}
template <typename InputIterator, typename Projection>
void assign_impl(InputIterator first, InputIterator last, T *capacityBegin, T *dst, T *dend,
Projection proj, std::forward_iterator_tag)
{
constexpr bool IsIdentity = std::is_same_v<Projection, q20::identity>;
const qsizetype n = std::distance(first, last);
if constexpr (IsIdentity && !QTypeInfo<T>::isComplex) {
// For non-complex types, we prefer a single std::copy() -> memcpy()
// call. We can do that because either the default constructor is
// trivial (so the lifetime has started) or the copy constructor is
// (and won't care what the stored value is).
std::copy(first, last, capacityBegin);
} else {
// There are two possibilities:
// 1) fewer elements than the current allocated space
// | prepend buffer | array | destroy |
// 2) more elements than the current allocated space
// | prepend buffer | array | construct |
//
// Both the prepend buffer and the current array may be empty.
// construct elements in the prepend buffer
while (first != last && capacityBegin != dst) {
q20::construct_at(capacityBegin, std::invoke(proj, *first));
++first;
++capacityBegin;
}
// overwrite elements in the existing array
while (first != last && dst != dend) {
*dst = std::invoke(proj, *first); // overwrite existing element
++first;
++dst;
}
// construct new elements in the append buffer
while (first != last) {
q20::construct_at(dst, std::invoke(proj, *first));
++first;
++dst;
}
// or destroy elements from the existing array
if (dst < dend)
std::destroy(dst, dend);
}
that()->size = n;
}
};
} // namespace QtPrivate
template <class T>
struct QArrayDataOps
: QtPrivate::QCommonArrayOps<T>
{
private:
using Base = QtPrivate::QCommonArrayOps<T>;
public:
using Base::Base;
QArrayDataOps *operator->() noexcept { return this; }
const QArrayDataOps *operator->() const noexcept { return this; }
};
QT_END_NAMESPACE
#endif // include guard