diff options
author | Marc Mutz <marc.mutz@qt.io> | 2023-06-02 16:22:37 +0200 |
---|---|---|
committer | Marc Mutz <marc.mutz@qt.io> | 2023-06-07 23:18:03 +0000 |
commit | f5ed163c19c4a165a61e6fbfdaf5ee39b5587a0c (patch) | |
tree | a8ab795999c4e6ec4bdc91ca26167564f6433cb6 | |
parent | 245c2b621f5942861b7f827bfc8a859b9efb9b72 (diff) |
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
Pick-to: 6.6
Change-Id: Icb44244cb08af391161c4309467d4e0d2d3d3d62
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
-rw-r--r-- | src/corelib/text/qstring.cpp | 28 | ||||
-rw-r--r-- | src/corelib/text/qstring.h | 48 | ||||
-rw-r--r-- | tests/auto/corelib/text/qstring/tst_qstring.cpp | 99 | ||||
-rw-r--r-- | tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp | 2 |
4 files changed, 177 insertions, 0 deletions
diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index de1409960a..ae6e6f67eb 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 <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()) { diff --git a/src/corelib/text/qstring.h b/src/corelib/text/qstring.h index 71b712c3e9..76833147d4 100644 --- a/src/corelib/text/qstring.h +++ b/src/corelib/text/qstring.h @@ -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); } diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index a6362d6a23..f3d2594e34 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -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" @@ -3424,11 +3426,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); @@ -3456,6 +3499,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() @@ -3484,6 +3564,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() diff --git a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp index 023a03f4a4..d683950445 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<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> |