QVarLengthArray: add STL-style assign()

Implemented assign() methods for QVarLengthArray to align
with the criteria of std::vector, addressing the previously
missing functionality.

Reference:
https://en.cppreference.com/w/cpp/container/vector/assign

[ChangeLog][QtCore][QVarLengthArray] Added assign().

Fixes: QTBUG-106200
Change-Id: If671069808ff561b0f4c77b6c7f7aca360a0c663
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Dennis Oberst 2022-12-21 14:12:05 +01:00
parent 65cda1f06a
commit 7cbdc8abbd
3 changed files with 175 additions and 2 deletions

View File

@ -231,6 +231,10 @@ protected:
}
}
void assign_impl(qsizetype prealloc, void *array, qsizetype n, const T &t);
template <typename Iterator>
void assign_impl(qsizetype prealloc, void *array, Iterator first, Iterator last);
bool isValidIterator(const const_iterator &i) const
{
const std::less<const T *> less = {};
@ -258,6 +262,8 @@ class QVarLengthArray
template <typename U>
using if_copyable = std::enable_if_t<std::is_copy_constructible_v<U>, bool>;
template <typename InputIterator>
using if_input_iterator = QtPrivate::IfIsInputIterator<InputIterator>;
public:
using size_type = typename Base::size_type;
using value_type = typename Base::value_type;
@ -319,7 +325,7 @@ public:
{
}
template <typename InputIterator, QtPrivate::IfIsInputIterator<InputIterator> = true>
template <typename InputIterator, if_input_iterator<InputIterator> = true>
inline QVarLengthArray(InputIterator first, InputIterator last)
: QVarLengthArray()
{
@ -480,6 +486,15 @@ public:
void insert(qsizetype i, T &&t);
void insert(qsizetype i, const T &t);
void insert(qsizetype i, qsizetype n, const T &t);
void assign(qsizetype n, const T &t)
{ Base::assign_impl(Prealloc, this->array, n, t); }
template <typename InputIterator, if_input_iterator<InputIterator> = true>
void assign(InputIterator first, InputIterator last)
{ Base::assign_impl(Prealloc, this->array, first, last); }
void assign(std::initializer_list<T> list)
{ assign(list.begin(), list.end()); }
#ifdef Q_QDOC
void replace(qsizetype i, const T &t);
void remove(qsizetype i, qsizetype n = 1);
@ -731,6 +746,51 @@ Q_OUTOFLINE_TEMPLATE void QVLABase<T>::append_impl(qsizetype prealloc, void *arr
this->s = asize;
}
template <class T>
Q_OUTOFLINE_TEMPLATE void QVLABase<T>::assign_impl(qsizetype prealloc, void *array, qsizetype n, const T &t)
{
Q_ASSERT(n >= 0);
if (n > capacity()) {
reallocate_impl(prealloc, array, 0, capacity()); // clear
resize_impl(prealloc, array, n, t);
} else {
auto mid = (std::min)(n, size());
std::fill(data(), data() + mid, t);
std::uninitialized_fill(data() + mid, data() + n, t);
s = n;
erase(data() + n, data() + size());
}
}
template <class T>
template <typename Iterator>
Q_OUTOFLINE_TEMPLATE void QVLABase<T>::assign_impl(qsizetype prealloc, void *array, Iterator first, Iterator last)
{
// This function only provides the basic exception guarantee.
if constexpr (std::is_convertible_v<typename std::iterator_traits<Iterator>::iterator_category, std::forward_iterator_tag>) {
const qsizetype n = std::distance(first, last);
if (n > capacity()) {
reallocate_impl(prealloc, array, 0, capacity()); // clear
reallocate_impl(prealloc, array, 0, n); // reserve n
}
}
auto dst = begin();
while (first != last && dst != end()) {
*dst = *first; // reuse initialized buffer
++dst;
++first;
}
qsizetype n = dst - begin();
while (first != last) {
emplace_back_impl(prealloc, array, *first);
++first;
++n;
}
erase(data() + n, data() + size());
}
template <class T>
Q_OUTOFLINE_TEMPLATE void QVLABase<T>::reallocate_impl(qsizetype prealloc, void *array, qsizetype asize, qsizetype aalloc)
{

View File

@ -103,11 +103,14 @@
lists.
*/
/*! \fn template<class T, qsizetype Prealloc> template<typename InputIterator> QVarLengthArray<T, Prealloc>::QVarLengthArray(InputIterator first, InputIterator last)
/*! \fn template<class T, qsizetype Prealloc> template<typename InputIterator, if_input_iterator<InputIterator>> QVarLengthArray<T, Prealloc>::QVarLengthArray(InputIterator first, InputIterator last)
\since 5.14
Constructs an array with the contents in the iterator range [\a first, \a last).
This constructor only participates in overload resolution if
\c InputIterator models the \c std::input_iterator concept.
The value type of \c InputIterator must be convertible to \c T.
*/
@ -992,3 +995,38 @@
\sa erase()
*/
/*! \fn template <class T, qsizetype Prealloc> void QVarLengthArray<T, Prealloc>::assign(qsizetype n, const T &t)
\since 6.6
Replaces the contents of this container with \a n copies of \a t.
The size of this container will be equal to \a n. This function will only
allocate memory if \a n exceeds the capacity of the container.
*/
/*! \fn template <class T, qsizetype Prealloc> template <typename InputIterator, if_input_iterator<InputIterator>> void QVarLengthArray<T, Prealloc>::assign(InputIterator first, InputIterator last)
\since 6.6
Replaces the contents of this container with a copy of the elements in the
iterator range [\a first, \a last).
The size of this container will be equal to the number of elements in the
range [\a first, \a last). This function will only allocate memory if the
number of elements in the range exceeds the capacity of the container.
This function overload only participates in overload resolution if
\c InputIterator models the \c std::input_iterator concept.
The behavior is undefined if either argument is an iterator into *this.
*/
/*! \fn template <class T, qsizetype Prealloc> void QVarLengthArray<T, Prealloc>::assign(std::initializer_list<T> list)
\since 6.6
Replaces the contents of this container with a copy of the elements of \a list.
The size of this container will be equal to the number of elements in \a list.
This function only allocates memory if the number of elements in \a list
exceeds the capacity of the container.
*/

View File

@ -331,6 +331,16 @@ private Q_SLOTS:
void resize_QString() { resize_impl<QString>(); }
void resize_QByteArray() { resize_impl<QByteArray>(); }
private:
template<typename Container>
void assign_impl() const;
private Q_SLOTS:
void assign_std_vector()
{ assign_impl<std::vector<int>>(); };
void assign_QVarLengthArray()
{ assign_impl<QVarLengthArray<int, 4>>(); };
private:
template <typename Container>
void front_back_impl() const;
@ -760,6 +770,71 @@ void tst_ContainerApiSymmetry::resize_impl() const
}
template <typename Container>
void tst_ContainerApiSymmetry::assign_impl() const
{
#define CHECK(Arr, ComparisonData, Sz_n, Sz_e, Cap_n, Cap_e) \
QCOMPARE(Sz_n, Sz_e); \
QCOMPARE(Cap_n, Cap_e); \
for (const auto &e : Arr) \
QCOMPARE(e, ComparisonData) \
/*end*/
using V = typename Container::value_type;
using S = typename Container::size_type;
auto tData = V(9);
{
// fill version
auto c = make<Container>(4);
c.assign(4, tData);
CHECK(c, tData, c.size(), S(4), c.capacity(), S(4));
c.assign(8, tData);
CHECK(c, tData, c.size(), S(8), c.capacity(), S(8));
c.assign(0, tData);
CHECK(c, tData, c.size(), S(0), c.capacity(), S(8));
}
{
// range version for non input iterator
auto c = make<Container>(4);
auto iter = make<Container>(1);
iter.assign(8, tData);
c.assign(iter.begin(), iter.end());
CHECK(c, tData, c.size(), S(8), c.capacity(), S(8));
}
{
// range version for input iterator
auto c = make<Container>(4);
std::stringstream ss("9 9 ");
c.assign(std::istream_iterator<V>{ss}, std::istream_iterator<V>{});
CHECK(c, tData, c.size(), S(2), c.capacity(), S(4));
ss.str("");
ss.clear();
ss << "9 9 9 9 ";
c.assign(std::istream_iterator<V>{ss}, std::istream_iterator<V>{});
CHECK(c, tData, c.size(), S(4), c.capacity(), S(4));
ss.str("");
ss.clear();
ss << "9 9 9 9 9 9 9 ";
c.assign(std::istream_iterator<V>{ss}, std::istream_iterator<V>{});
// We cannot check the capacity here because growth rates differ between implementations.
CHECK(c, tData, c.size(), S(7), 8, S(8));
}
{
// initializer-list version
auto c = make<Container>(4);
std::initializer_list<V> list = {tData, tData, tData};
c.assign(list);
CHECK(c, tData, c.size(), S(3), c.capacity(), S(4));
}
#undef CHECK
}
template<typename Container>
void tst_ContainerApiSymmetry::front_back_impl() const
{
using V = typename Container::value_type;