QString: add STL-style assign() [2/4]: (it,it) overload for QChar-convertible *it

Restrict the permissible value_types to those QStringView can take,
plus QLatin1Char. All of these implicitly convert to QChar and give
the correct result, even when converted char-by-char.

Task-number: QTBUG-106198
Change-Id: Icb44244cb08af391161c4309467d4e0d2d3d3d62
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
(cherry picked from commit f5ed163c19c4a165a61e6fbfdaf5ee39b5587a0c)
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2023-06-02 16:22:37 +02:00 committed by Thiago Macieira
parent e5d222867c
commit a08ca88b24
4 changed files with 177 additions and 0 deletions

View File

@ -3339,6 +3339,34 @@ QString &QString::append(QChar ch)
\sa fill()
*/
/*!
\fn template <typename InputIterator, if_compatible_iterator<InputIterator>> QString &QString::assign(InputIterator first, InputIterator last)
\since 6.6
Replaces the contents of this string with a copy of the elements in the
iterator range [\a first, \a last) and returns a reference to this string.
The size of this string 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 this string or this string is shared.
\note This function overload only participates in overload resolution if
\c InputIterator meets the requirements of a
\l {https://en.cppreference.com/w/cpp/named_req/InputIterator} {LegacyInputIterator}
and the \c{value_type} of \c InputIterator is one of the following character types:
\list
\li QChar
\li QLatin1Char
\li \c char16_t
\li (on platforms, such as Windows, where it is a 16-bit type) \c wchar_t
\endlist
\note The behavior is undefined if either argument is an iterator into *this or
[\a first, \a last) is not a valid range.
*/
QString &QString::assign(QAnyStringView s)
{
if (s.size() <= capacity() && isDetached()) {

View File

@ -24,6 +24,7 @@
#include <string>
#include <iterator>
#include <QtCore/q20memory.h>
#include <stdarg.h>
@ -120,6 +121,33 @@ class Q_CORE_EXPORT QString
typedef QTypedArrayData<char16_t> Data;
friend class ::tst_QString;
template <typename Iterator>
static constexpr bool is_contiguous_iterator_v =
// Can't use contiguous_iterator_tag here, as STL impls can't agree on feature macro.
// To avoid differences in C++20 and C++17 builds, treat only pointers as contiguous
// for now:
// std::contiguous_iterator<Iterator>;
std::is_pointer_v<Iterator>;
template <typename Char>
using is_compatible_char_helper = std::disjunction<
QtPrivate::IsCompatibleCharType<Char>,
std::is_same<Char, QLatin1Char> // special case
>;
template <typename Iterator>
static constexpr bool is_compatible_iterator_v = std::conjunction_v<
std::is_convertible<
typename std::iterator_traits<Iterator>::iterator_category,
std::input_iterator_tag
>,
is_compatible_char_helper<typename std::iterator_traits<Iterator>::value_type>
>;
template <typename Iterator>
using if_compatible_iterator = std::enable_if_t<is_compatible_iterator_v<Iterator>, bool>;
public:
typedef QStringPrivate DataPointer;
@ -385,6 +413,26 @@ public:
Q_ASSERT(n >= 0);
return fill(c, n);
}
template <typename InputIterator, if_compatible_iterator<InputIterator> = true>
QString &assign(InputIterator first, InputIterator last)
{
using V = typename std::iterator_traits<InputIterator>::value_type;
constexpr bool IsL1C = std::is_same_v<std::remove_cv_t<V>, QLatin1Char>;
if constexpr (is_contiguous_iterator_v<InputIterator>) {
const auto p = q20::to_address(first);
const auto len = qsizetype(last - first);
if constexpr (IsL1C)
return assign(QLatin1StringView(reinterpret_cast<const char*>(p), len));
else
return assign(QAnyStringView(p, len));
} else { // non-contiguous iterator, need to feed data piecemeal
d.assign(first, last, [](QChar ch) -> char16_t { return ch.unicode(); });
d.data()[d.size] = u'\0';
return *this;
}
}
inline QString &operator+=(QChar c) { return append(c); }
inline QString &operator+=(const QString &s) { return append(s); }

View File

@ -27,9 +27,11 @@
#include <qhash.h>
#include <private/qtools_p.h>
#include <forward_list>
#include <string>
#include <algorithm>
#include <limits>
#include <sstream>
#include "../shared/test_number_shared.h"
#include "../../../../shared/localechange.h"
@ -3422,11 +3424,52 @@ void tst_QString::assign()
QCOMPARE(str.assign(3, u'x'), u"xxx");
QCOMPARE(str.size(), 3);
}
// QString &assign(InputIterator, InputIterator)
{
// Forward iterator versions
QString str;
const QString tstr = QString::fromUtf8(u8"(ノಠ益ಠ)\0ノ彡┻━┻");
QCOMPARE(str.assign(tstr.begin(), tstr.end()), u"(ノಠ益ಠ)\0ノ彡┻━┻");
QCOMPARE(str.size(), 6);
const char16_t c16[] = u"٩(⁎❛ᴗ❛⁎)۶ 🤷";
str.assign(std::begin(c16), std::end(c16) - 1);
QCOMPARE(str, c16);
std::u16string c16str(c16);
str.assign(c16str.begin(), c16str.end());
QCOMPARE(str, c16);
QVarLengthArray<QLatin1Char, 5> l1ch = {'F'_L1, 'G'_L1, 'H'_L1, 'I'_L1, 'J'_L1};
str.assign(l1ch.begin(), l1ch.end());
QCOMPARE(str, u"FGHIJ");
std::forward_list<QChar> qch = {u'G', u'H', u'I', u'J', u'K'};
str.assign(qch.begin(), qch.end());
QCOMPARE(str, u"GHIJK");
const QList<char16_t> qch16 = {u'X', u'H', u'I', u'J', u'K'}; // QList<T>::iterator need not be T*
str.assign(qch16.begin(), qch16.end());
QCOMPARE(str, u"XHIJK");
#if defined(Q_OS_WIN)
QVarLengthArray<wchar_t> wch = {L'A', L'B', L'C', L'D', L'E'};
str.assign(wch.begin(), wch.end());
QCOMPARE(str, u"ABCDE");
#endif
// Input iterator versions
std::stringstream ss("50 51 52 53 54");
str.assign(std::istream_iterator<ushort>{ss}, std::istream_iterator<ushort>{});
QCOMPARE(str, u"23456");
}
// Test chaining
{
QString str;
QString tstr = u"TEST DATA"_s;
str.assign(tstr.begin(), tstr.end()).assign({"Hello World!"}).assign(5, u'T');
QCOMPARE(str, u"TTTTT");
QCOMPARE(str.size(), 5);
QCOMPARE(str.assign(300, u'T').assign({"[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]"}), u"[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]");
QCOMPARE(str.size(), 19);
QCOMPARE(str.assign(10, u'c').assign(str.begin(), str.end()), str);
QCOMPARE(str.size(), 10);
QCOMPARE(str.assign("data").assign(QByteArrayView::fromArray(
{std::byte('T'), std::byte('T'), std::byte('T')})), u"TTT");
QCOMPARE(str.size(), 3);
@ -3454,6 +3497,43 @@ void tst_QString::assign_shared()
QCOMPARE(str, u"DDDD");
QCOMPARE(strCopy, u"DATA");
}
{
QString str = "DATA"_L1;
QVERIFY(str.isDetached());
auto copyForwardIt = str;
QVERIFY(!str.isDetached());
QVERIFY(!copyForwardIt.isDetached());
QVERIFY(str.isSharedWith(copyForwardIt));
QVERIFY(copyForwardIt.isSharedWith(str));
QString tstr = u"DDDD"_s;
str.assign(tstr.begin(), tstr.end());
QVERIFY(str.isDetached());
QVERIFY(copyForwardIt.isDetached());
QVERIFY(!str.isSharedWith(copyForwardIt));
QVERIFY(!copyForwardIt.isSharedWith(str));
QCOMPARE(str, u"DDDD");
QCOMPARE(copyForwardIt, u"DATA");
}
{
QString str = "DATA"_L1;
QVERIFY(str.isDetached());
auto copyInputIt = str;
QVERIFY(!str.isDetached());
QVERIFY(!copyInputIt.isDetached());
QVERIFY(str.isSharedWith(copyInputIt));
QVERIFY(copyInputIt.isSharedWith(str));
std::stringstream ss("49 50 51 52 53 54 ");
str.assign(std::istream_iterator<ushort>{ss}, std::istream_iterator<ushort>{});
QVERIFY(str.isDetached());
QVERIFY(copyInputIt.isDetached());
QVERIFY(!str.isSharedWith(copyInputIt));
QVERIFY(!copyInputIt.isSharedWith(str));
QCOMPARE(str, u"123456");
QCOMPARE(copyInputIt, u"DATA");
}
}
void tst_QString::assign_uses_prepend_buffer()
@ -3482,6 +3562,25 @@ void tst_QString::assign_uses_prepend_buffer()
QCOMPARE_EQ(capEnd(withFreeSpaceAtBegin), oldCapEnd);
QCOMPARE(withFreeSpaceAtBegin, test);
}
// QString &assign(InputIterator, InputIterator)
{
QString withFreeSpaceAtBegin;
for (int i = 0; i < 100 && withFreeSpaceAtBegin.d.freeSpaceAtBegin() < 2; ++i)
withFreeSpaceAtBegin.prepend(u'd');
QCOMPARE_GT(withFreeSpaceAtBegin.d.freeSpaceAtBegin(), 1);
const auto oldCapBegin = capBegin(withFreeSpaceAtBegin);
const auto oldCapEnd = capEnd(withFreeSpaceAtBegin);
std::stringstream ss;
for (qsizetype i = 0; i < withFreeSpaceAtBegin.d.freeSpaceAtBegin(); ++i)
ss << "d ";
withFreeSpaceAtBegin.assign(std::istream_iterator<ushort>{ss}, std::istream_iterator<ushort>{});
QCOMPARE_EQ(withFreeSpaceAtBegin.d.freeSpaceAtBegin(), 0); // we used the prepend buffer
QCOMPARE_EQ(capBegin(withFreeSpaceAtBegin), oldCapBegin);
QCOMPARE_EQ(capEnd(withFreeSpaceAtBegin), oldCapEnd);
}
}
void tst_QString::operator_pluseq_special_cases()

View File

@ -337,9 +337,11 @@ private:
private Q_SLOTS:
void assign_std_vector() { assign_impl<std::vector<int>>(); };
void assign_std_string() { assign_impl<std::string>(); }
void assign_QVarLengthArray() { assign_impl<QVarLengthArray<int, 4>>(); };
void assign_QList() { assign_impl<QList<int>>(); }
void assign_QByteArray() { assign_impl<QByteArray>(); }
void assign_QString() { assign_impl<QString>(); }
private:
template <typename Container>