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(); 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 \fn QByteArray::chopped(int len) const
\since 5.10 \since 5.10

View File

@ -224,8 +224,17 @@ public:
Q_REQUIRED_RESULT QByteArray left(int len) const; Q_REQUIRED_RESULT QByteArray left(int len) const;
Q_REQUIRED_RESULT QByteArray right(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 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_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(const QByteArray &a) const;
bool startsWith(char c) const; bool startsWith(char c) const;

View File

@ -4600,11 +4600,59 @@ QString QString::mid(int position, int n) const
return QString(); 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 \fn QString QString::chopped(int len) const
\since 5.10 \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. of this string.
\note The behavior is undefined if \a len is negative or greater than size(). \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 left(int n) const;
Q_REQUIRED_RESULT QString right(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 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_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; Q_REQUIRED_RESULT QStringRef leftRef(int n) const;

View File

@ -656,6 +656,54 @@ QT_BEGIN_NAMESPACE
\sa mid(), left(), chopped(), chop(), truncate() \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 \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); } { return Q_ASSERT(n >= 0), Q_ASSERT(n <= size()), QStringView(m_data, n); }
Q_REQUIRED_RESULT Q_DECL_CONSTEXPR QStringView right(qsizetype n) const 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); } { 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 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); } { 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(); void right_data();
template <typename String> void right_impl(); 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(); void chop_data();
template <typename String> void chop_impl(); template <typename String> void chop_impl();
@ -673,6 +682,27 @@ private Q_SLOTS:
void right_QByteArray_data() { right_data(); } void right_QByteArray_data() { right_data(); }
void right_QByteArray() { right_impl<QByteArray>(); } 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_data() { chop_data(); }
void chop_QString() { chop_impl<QString>(); } void chop_QString() { chop_impl<QString>(); }
void chop_QStringRef_data() { chop_data(); } void chop_QStringRef_data() { chop_data(); }
@ -1502,43 +1532,7 @@ void tst_QStringApiSymmetry::tok_impl() const
void tst_QStringApiSymmetry::mid_data() void tst_QStringApiSymmetry::mid_data()
{ {
QTest::addColumn<QStringRef>("unicode"); slice_data();
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> template <typename String>
@ -1583,31 +1577,7 @@ void tst_QStringApiSymmetry::mid_impl()
void tst_QStringApiSymmetry::left_data() void tst_QStringApiSymmetry::left_data()
{ {
QTest::addColumn<QStringRef>("unicode"); first_data();
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> template <typename String>
@ -1648,31 +1618,7 @@ void tst_QStringApiSymmetry::left_impl()
void tst_QStringApiSymmetry::right_data() void tst_QStringApiSymmetry::right_data()
{ {
QTest::addColumn<QStringRef>("unicode"); last_data();
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
} }
template <typename String> 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() void tst_QStringApiSymmetry::chop_data()
{ {
QTest::addColumn<QStringRef>("unicode"); QTest::addColumn<QStringRef>("unicode");
@ -1710,7 +1859,7 @@ void tst_QStringApiSymmetry::chop_data()
QTest::addColumn<int>("n"); QTest::addColumn<int>("n");
QTest::addColumn<QStringRef>("result"); 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); QTest::addRow("empty") << QStringRef(&empty) << QLatin1String("") << 0 << QStringRef(&empty);
// Some classes' truncate() implementations have a wide contract, others a narrow one // Some classes' truncate() implementations have a wide contract, others a narrow one