Implement first/last/from and slice() for string-like classes

These methods are scheduled as a replacement for left/right/mid()
in Qt 6 with a consistent, narrow contract that does not allow
out of bounds indices, and therefore does permit faster
implementations.

Change-Id: Iabf22e8d4f3fef3c5e69a17f103e6cddebe420b1
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Lars Knoll 2020-06-03 21:59:19 +02:00
parent 6ec41bd550
commit 38096a3d70
7 changed files with 410 additions and 91 deletions

View File

@ -2988,6 +2988,53 @@ QByteArray QByteArray::mid(int pos, int len) const
return QByteArray();
}
/*!
\fn QByteArray QByteArray::first(qsizetype n) const
\since 6.0
Returns the first \a n bytes of the byte array.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa last(), slice(), from(), startsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QByteArray QByteArray::last(qsizetype n) const
\since 6.0
Returns the last \a n bytes of the byte array.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa first(), slice(), from(), endsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QByteArray QByteArray::slice(qsizetype pos, qsizetype n) const
\since 6.0
Returns a byte array containing the \a n bytes of this object starting
at position \a pos.
\note The behavior is undefined when \a pos < 0, \a n < 0,
or \a pos + \a n > size().
\sa first(), last(), from(), chopped(), chop(), truncate()
*/
/*!
\fn QByteArray QByteArray::from(qsizetype pos) const
\since 6.0
Returns a byte array containing the bytes starting at position \a pos
in this object, and extending to the end of this object.
\note The behavior is undefined when \a pos < 0 or \a pos > size().
\sa first(), last(), slice(), chopped(), chop(), truncate()
*/
/*!
\fn QByteArray::chopped(int len) const
\since 5.10

View File

@ -224,8 +224,17 @@ public:
Q_REQUIRED_RESULT QByteArray left(int len) const;
Q_REQUIRED_RESULT QByteArray right(int len) const;
Q_REQUIRED_RESULT QByteArray mid(int index, int len = -1) const;
Q_REQUIRED_RESULT QByteArray first(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QByteArray(data(), int(n)); }
Q_REQUIRED_RESULT QByteArray last(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QByteArray(data() + size() - n, int(n)); }
Q_REQUIRED_RESULT QByteArray from(qsizetype pos) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(pos <= size()); return QByteArray(data() + pos, size() - int(pos)); }
Q_REQUIRED_RESULT QByteArray slice(qsizetype pos, qsizetype n) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(n >= 0); Q_ASSERT(size_t(pos) + size_t(n) <= size_t(size())); return QByteArray(data() + pos, int(n)); }
Q_REQUIRED_RESULT QByteArray chopped(int len) const
{ Q_ASSERT(len >= 0); Q_ASSERT(len <= size()); return left(size() - len); }
{ Q_ASSERT(len >= 0); Q_ASSERT(len <= size()); return first(size() - len); }
bool startsWith(const QByteArray &a) const;
bool startsWith(char c) const;

View File

@ -4600,11 +4600,59 @@ QString QString::mid(int position, int n) const
return QString();
}
/*!
\fn QString QString::first(qsizetype n) const
\since 6.0
Returns a string that contains the first \a n characters
of this string.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa last(), slice(), from(), startsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QString QString::last(qsizetype n) const
\since 6.0
Returns the string that contains the last \a n characters of this string.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa first(), slice(), from(), endsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QString QString::slice(qsizetype pos, qsizetype n) const
\since 6.0
Returns a string that contains \a n characters of this string,
starting at position \a pos.
\note The behavior is undefined when \a pos < 0, \a n < 0,
or \a pos + \a n > size().
\sa first(), last(), chopped(), chop(), truncate()
*/
/*!
\fn QString QString::from(qsizetype pos) const
\since 6.0
Returns a string that contains the portion of this string starting at
position \a pos and extending to its end.
\note The behavior is undefined when \a pos < 0 or \a pos > size().
\sa first(), last(), slice(), chopped(), chop(), truncate()
*/
/*!
\fn QString QString::chopped(int len) const
\since 5.10
Returns a substring that contains the size() - \a len leftmost characters
Returns a string that contains the size() - \a len leftmost characters
of this string.
\note The behavior is undefined if \a len is negative or greater than size().

View File

@ -455,8 +455,17 @@ public:
Q_REQUIRED_RESULT QString left(int n) const;
Q_REQUIRED_RESULT QString right(int n) const;
Q_REQUIRED_RESULT QString mid(int position, int n = -1) const;
Q_REQUIRED_RESULT QString first(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QString(data(), int(n)); }
Q_REQUIRED_RESULT QString last(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QString(data() + size() - n, int(n)); }
Q_REQUIRED_RESULT QString from(qsizetype pos) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(pos <= size()); return QString(data() + pos, size() - int(pos)); }
Q_REQUIRED_RESULT QString slice(qsizetype pos, qsizetype n) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(n >= 0); Q_ASSERT(size_t(pos) + size_t(n) <= size_t(size())); return QString(data() + pos, int(n)); }
Q_REQUIRED_RESULT QString chopped(int n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return left(size() - n); }
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return first(size() - n); }
Q_REQUIRED_RESULT QStringRef leftRef(int n) const;

View File

@ -656,6 +656,54 @@ QT_BEGIN_NAMESPACE
\sa mid(), left(), chopped(), chop(), truncate()
*/
/*!
\fn QStringView QStringView::first(qsizetype n) const
\since 6.0
Returns a string view that points to the first \a n characters
of this string.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa last(), subString(), startsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QStringView QStringView::last(qsizetype n) const
\since 6.0
Returns a string view that points to the last \a n characters of this string.
\note The behavior is undefined when \a n < 0 or \a n > size().
\sa first(), subString(), endsWith(), chopped(), chop(), truncate()
*/
/*!
\fn QStringView QStringView::slice(qsizetype pos, qsizetype n) const
\since 6.0
Returns a string view that points to \a n characters of this string,
starting at position \a pos.
\note The behavior is undefined when \a pos < 0, \a n < 0,
or \a pos + \a n > size().
\sa first(), last(), chopped(), chop(), truncate()
*/
/*!
\fn QStringView QStringView::from(qsizetype pos) const
\since 6.0
Returns a string view starting at position \a pos in this object,
and extending to its end.
\note The behavior is undefined when \a pos < 0 or \a pos > size().
\sa first(), last(), chopped(), chop(), truncate()
*/
/*!
\fn QStringView QStringView::chopped(qsizetype length) const

View File

@ -265,6 +265,15 @@ public:
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data, n); }
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView right(qsizetype n) const
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data + m_size - n, n); }
Q_REQUIRED_RESULT constexpr QStringView first(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QStringView(m_data, int(n)); }
Q_REQUIRED_RESULT constexpr QStringView last(qsizetype n) const
{ Q_ASSERT(n >= 0); Q_ASSERT(n <= size()); return QStringView(m_data + size() - n, int(n)); }
Q_REQUIRED_RESULT constexpr QStringView from(qsizetype pos) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(pos <= size()); return QStringView(m_data + pos, size() - int(pos)); }
Q_REQUIRED_RESULT constexpr QStringView slice(qsizetype pos, qsizetype n) const
{ Q_ASSERT(pos >= 0); Q_ASSERT(n >= 0); Q_ASSERT(size_t(pos) + size_t(n) <= size_t(size())); return QStringView(m_data + pos, int(n)); }
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView chopped(qsizetype n) const
{ return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data, m_size - n); }

View File

@ -632,6 +632,15 @@ private:
void right_data();
template <typename String> void right_impl();
void slice_data();
template <typename String> void slice_impl();
void first_data();
template <typename String> void first_impl();
void last_data();
template <typename String> void last_impl();
void chop_data();
template <typename String> void chop_impl();
@ -673,6 +682,27 @@ private Q_SLOTS:
void right_QByteArray_data() { right_data(); }
void right_QByteArray() { right_impl<QByteArray>(); }
void slice_QString_data() { slice_data(); }
void slice_QString() { slice_impl<QString>(); }
void slice_QStringView_data() { slice_data(); }
void slice_QStringView() { slice_impl<QStringView>(); }
void slice_QByteArray_data() { slice_data(); }
void slice_QByteArray() { slice_impl<QByteArray>(); }
void first_truncate_QString_data() { first_data(); }
void first_truncate_QString() { first_impl<QString>(); }
void first_truncate_QStringView_data() { first_data(); }
void first_truncate_QStringView() { first_impl<QStringView>(); }
void first_truncate_QByteArray_data() { first_data(); }
void first_truncate_QByteArray() { first_impl<QByteArray>(); }
void last_QString_data() { last_data(); }
void last_QString() { last_impl<QString>(); }
void last_QStringView_data() { last_data(); }
void last_QStringView() { last_impl<QStringView>(); }
void last_QByteArray_data() { last_data(); }
void last_QByteArray() { last_impl<QByteArray>(); }
void chop_QString_data() { chop_data(); }
void chop_QString() { chop_impl<QString>(); }
void chop_QStringRef_data() { chop_data(); }
@ -1502,43 +1532,7 @@ void tst_QStringApiSymmetry::tok_impl() const
void tst_QStringApiSymmetry::mid_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("pos");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
QTest::addColumn<QStringRef>("result2");
QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << 0 << QStringRef() << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << 0 << QStringRef(&empty) << QStringRef(&empty);
// Some classes' mid() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, p, n, r1, r2) \
QTest::addRow("%s%d%d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2)
ROW(a, 0, 0, a, empty);
ROW(a, 0, 1, a, a);
ROW(a, 1, 0, empty, empty);
ROW(ab, 0, 0, ab, empty);
ROW(ab, 0, 1, ab, a);
ROW(ab, 0, 2, ab, ab);
ROW(ab, 1, 0, b, empty);
ROW(ab, 1, 1, b, b);
ROW(ab, 2, 0, empty, empty);
ROW(abc, 0, 0, abc, empty);
ROW(abc, 0, 1, abc, a);
ROW(abc, 0, 2, abc, ab);
ROW(abc, 0, 3, abc, abc);
ROW(abc, 1, 0, bc, empty);
ROW(abc, 1, 1, bc, b);
ROW(abc, 1, 2, bc, bc);
ROW(abc, 2, 0, c, empty);
ROW(abc, 2, 1, c, c);
ROW(abc, 3, 0, empty, empty);
#undef ROW
slice_data();
}
template <typename String>
@ -1583,31 +1577,7 @@ void tst_QStringApiSymmetry::mid_impl()
void tst_QStringApiSymmetry::left_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' left() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, 0, empty);
ROW(a, 1, a);
ROW(ab, 0, empty);
ROW(ab, 1, a);
ROW(ab, 2, ab);
ROW(abc, 0, empty);
ROW(abc, 1, a);
ROW(abc, 2, ab);
ROW(abc, 3, abc);
#undef ROW
first_data();
}
template <typename String>
@ -1648,31 +1618,7 @@ void tst_QStringApiSymmetry::left_impl()
void tst_QStringApiSymmetry::right_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' right() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, 0, empty);
ROW(a, 1, a);
ROW(ab, 0, empty);
ROW(ab, 1, b);
ROW(ab, 2, ab);
ROW(abc, 0, empty);
ROW(abc, 1, c);
ROW(abc, 2, bc);
ROW(abc, 3, abc);
#undef ROW
last_data();
}
template <typename String>
@ -1703,6 +1649,209 @@ void tst_QStringApiSymmetry::right_impl()
}
}
void tst_QStringApiSymmetry::slice_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("pos");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
QTest::addColumn<QStringRef>("result2");
// QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << 0 << QStringRef() << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << 0 << QStringRef(&empty) << QStringRef(&empty);
// Some classes' mid() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, p, n, r1, r2) \
QTest::addRow("%s%d%d", #base, p, n) << QStringRef(&base) << QLatin1String(#base) << p << n << QStringRef(&r1) << QStringRef(&r2)
ROW(a, 0, 0, a, empty);
ROW(a, 0, 1, a, a);
ROW(a, 1, 0, empty, empty);
ROW(ab, 0, 0, ab, empty);
ROW(ab, 0, 1, ab, a);
ROW(ab, 0, 2, ab, ab);
ROW(ab, 1, 0, b, empty);
ROW(ab, 1, 1, b, b);
ROW(ab, 2, 0, empty, empty);
ROW(abc, 0, 0, abc, empty);
ROW(abc, 0, 1, abc, a);
ROW(abc, 0, 2, abc, ab);
ROW(abc, 0, 3, abc, abc);
ROW(abc, 1, 0, bc, empty);
ROW(abc, 1, 1, bc, b);
ROW(abc, 1, 2, bc, bc);
ROW(abc, 2, 0, c, empty);
ROW(abc, 2, 1, c, c);
ROW(abc, 3, 0, empty, empty);
#undef ROW
}
template <typename String>
void tst_QStringApiSymmetry::slice_impl()
{
QFETCH(const QStringRef, unicode);
QFETCH(const QLatin1String, latin1);
QFETCH(const int, pos);
QFETCH(const int, n);
QFETCH(const QStringRef, result);
QFETCH(const QStringRef, result2);
const auto utf8 = unicode.toUtf8();
const auto s = make<String>(unicode, latin1, utf8);
{
const auto from = s.from(pos);
const auto slice = s.slice(pos, n);
QCOMPARE(from, result);
QCOMPARE(from.isNull(), result.isNull());
QCOMPARE(from.isEmpty(), result.isEmpty());
QCOMPARE(slice, result2);
QCOMPARE(slice.isNull(), result2.isNull());
QCOMPARE(slice.isEmpty(), result2.isEmpty());
}
{
const auto from = detached(s).from(pos);
const auto slice = detached(s).slice(pos, n);
QCOMPARE(from, result);
QCOMPARE(from.isNull(), result.isNull());
QCOMPARE(from.isEmpty(), result.isEmpty());
QCOMPARE(slice, result2);
QCOMPARE(slice.isNull(), result2.isNull());
QCOMPARE(slice.isEmpty(), result2.isEmpty());
}
}
void tst_QStringApiSymmetry::first_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
// QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' left() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, 0, empty);
ROW(a, 1, a);
ROW(ab, 0, empty);
ROW(ab, 1, a);
ROW(ab, 2, ab);
ROW(abc, 0, empty);
ROW(abc, 1, a);
ROW(abc, 2, ab);
ROW(abc, 3, abc);
#undef ROW
}
template <typename String>
void tst_QStringApiSymmetry::first_impl()
{
QFETCH(const QStringRef, unicode);
QFETCH(const QLatin1String, latin1);
QFETCH(const int, n);
QFETCH(const QStringRef, result);
const auto utf8 = unicode.toUtf8();
const auto s = make<String>(unicode, latin1, utf8);
{
const auto first = s.first(n);
QCOMPARE(first, result);
QCOMPARE(first.isNull(), result.isNull());
QCOMPARE(first.isEmpty(), result.isEmpty());
}
{
const auto first = detached(s).first(n);
QCOMPARE(first, result);
QCOMPARE(first.isNull(), result.isNull());
QCOMPARE(first.isEmpty(), result.isEmpty());
}
{
auto first = s;
first.truncate(n);
QCOMPARE(first, result);
QCOMPARE(first.isNull(), result.isNull());
QCOMPARE(first.isEmpty(), result.isEmpty());
}
}
void tst_QStringApiSymmetry::last_data()
{
QTest::addColumn<QStringRef>("unicode");
QTest::addColumn<QLatin1String>("latin1");
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
// QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' last() implementations have a wide contract, others a narrow one
// so only test valid arguents here:
#define ROW(base, n, res) \
QTest::addRow("%s%d", #base, n) << QStringRef(&base) << QLatin1String(#base) << n << QStringRef(&res);
ROW(a, 0, empty);
ROW(a, 1, a);
ROW(ab, 0, empty);
ROW(ab, 1, b);
ROW(ab, 2, ab);
ROW(abc, 0, empty);
ROW(abc, 1, c);
ROW(abc, 2, bc);
ROW(abc, 3, abc);
#undef ROW
}
template <typename String>
void tst_QStringApiSymmetry::last_impl()
{
QFETCH(const QStringRef, unicode);
QFETCH(const QLatin1String, latin1);
QFETCH(const int, n);
QFETCH(const QStringRef, result);
const auto utf8 = unicode.toUtf8();
const auto s = make<String>(unicode, latin1, utf8);
{
const auto last = s.last(n);
QCOMPARE(last, result);
QCOMPARE(last.isNull(), result.isNull());
QCOMPARE(last.isEmpty(), result.isEmpty());
}
{
const auto last = detached(s).last(n);
QCOMPARE(last, result);
QCOMPARE(last.isNull(), result.isNull());
QCOMPARE(last.isEmpty(), result.isEmpty());
}
}
void tst_QStringApiSymmetry::chop_data()
{
QTest::addColumn<QStringRef>("unicode");
@ -1710,7 +1859,7 @@ void tst_QStringApiSymmetry::chop_data()
QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result");
QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
// QTest::addRow("null") << QStringRef() << QLatin1String() << 0 << QStringRef();
QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' truncate() implementations have a wide contract, others a narrow one