diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index de1409960a3..ae6e6f67ebc 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -3339,6 +3339,34 @@ QString &QString::append(QChar ch) \sa fill() */ +/*! + \fn template > 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()) { diff --git a/src/corelib/text/qstring.h b/src/corelib/text/qstring.h index 71b712c3e9f..76833147d42 100644 --- a/src/corelib/text/qstring.h +++ b/src/corelib/text/qstring.h @@ -24,6 +24,7 @@ #include #include +#include #include @@ -120,6 +121,33 @@ class Q_CORE_EXPORT QString typedef QTypedArrayData Data; friend class ::tst_QString; + + template + 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; + std::is_pointer_v; + + template + using is_compatible_char_helper = std::disjunction< + QtPrivate::IsCompatibleCharType, + std::is_same // special case + >; + + template + static constexpr bool is_compatible_iterator_v = std::conjunction_v< + std::is_convertible< + typename std::iterator_traits::iterator_category, + std::input_iterator_tag + >, + is_compatible_char_helper::value_type> + >; + + template + using if_compatible_iterator = std::enable_if_t, bool>; + public: typedef QStringPrivate DataPointer; @@ -385,6 +413,26 @@ public: Q_ASSERT(n >= 0); return fill(c, n); } + template = true> + QString &assign(InputIterator first, InputIterator last) + { + using V = typename std::iterator_traits::value_type; + constexpr bool IsL1C = std::is_same_v, QLatin1Char>; + + if constexpr (is_contiguous_iterator_v) { + const auto p = q20::to_address(first); + const auto len = qsizetype(last - first); + if constexpr (IsL1C) + return assign(QLatin1StringView(reinterpret_cast(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); } diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index a99b354b952..d15be52e413 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -27,9 +27,11 @@ #include #include +#include #include #include #include +#include #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 l1ch = {'F'_L1, 'G'_L1, 'H'_L1, 'I'_L1, 'J'_L1}; + str.assign(l1ch.begin(), l1ch.end()); + QCOMPARE(str, u"FGHIJ"); + std::forward_list qch = {u'G', u'H', u'I', u'J', u'K'}; + str.assign(qch.begin(), qch.end()); + QCOMPARE(str, u"GHIJK"); + const QList qch16 = {u'X', u'H', u'I', u'J', u'K'}; // QList::iterator need not be T* + str.assign(qch16.begin(), qch16.end()); + QCOMPARE(str, u"XHIJK"); +#if defined(Q_OS_WIN) + QVarLengthArray 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{ss}, std::istream_iterator{}); + 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{ss}, std::istream_iterator{}); + 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{ss}, std::istream_iterator{}); + 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() diff --git a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp index 023a03f4a4e..d6839504452 100644 --- a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp +++ b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp @@ -337,9 +337,11 @@ private: private Q_SLOTS: void assign_std_vector() { assign_impl>(); }; + void assign_std_string() { assign_impl(); } void assign_QVarLengthArray() { assign_impl>(); }; void assign_QList() { assign_impl>(); } void assign_QByteArray() { assign_impl(); } + void assign_QString() { assign_impl(); } private: template