QString: towards QStringView::arg() pt.3: Long live QStringView/QLatin1String::arg()

This version of arg(), unlike its QString counterpart, transparently accepts
views without conversion to QString, and is also extensible to further argument
types, say a future QFormattedNumber.

[ChangeLog][QtCore][QStringView/QLatin1String] Added arg(), taking arbitrarily
many strings.

Change-Id: If40ef3c445f63383e32573f3f515fdda84c7fe3a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2019-06-03 11:43:24 +02:00
parent 6f84829031
commit 35431062bd
6 changed files with 246 additions and 23 deletions

View File

@ -8754,19 +8754,23 @@ QString QString::arg(double a, int fieldWidth, char fmt, int prec, QChar fillCha
return replaceArgEscapes(*this, d, fieldWidth, arg, locale_arg, fillChar);
}
static int getEscape(const QChar *uc, qsizetype *pos, qsizetype len, int maxNumber = 999)
static inline ushort to_unicode(const QChar c) { return c.unicode(); }
static inline ushort to_unicode(const char c) { return QLatin1Char{c}.unicode(); }
template <typename Char>
static int getEscape(const Char *uc, qsizetype *pos, qsizetype len, int maxNumber = 999)
{
int i = *pos;
++i;
if (i < len && uc[i] == QLatin1Char('L'))
++i;
if (i < len) {
int escape = uc[i].unicode() - '0';
int escape = to_unicode(uc[i]) - '0';
if (uint(escape) >= 10U)
return -1;
++i;
while (i < len) {
int digit = uc[i].unicode() - '0';
int digit = to_unicode(uc[i]) - '0';
if (uint(digit) >= 10U)
break;
escape = (escape * 10) + digit;
@ -8817,18 +8821,23 @@ static int getEscape(const QChar *uc, qsizetype *pos, qsizetype len, int maxNumb
namespace {
struct Part
{
Q_DECL_CONSTEXPR Part() : string{}, number{0} {}
Part() = default; // for QVarLengthArray; do not use
Q_DECL_CONSTEXPR Part(QStringView s, int num = -1)
: string{s}, number{num} {}
: tag{QtPrivate::ArgBase::U16}, number{num}, data{s.utf16()}, size{s.size()} {}
Q_DECL_CONSTEXPR Part(QLatin1String s, int num = -1)
: tag{QtPrivate::ArgBase::L1}, number{num}, data{s.data()}, size{s.size()} {}
QStringView string;
void reset(QStringView s) noexcept { *this = {s, number}; }
void reset(QLatin1String s) noexcept { *this = {s, number}; }
QtPrivate::ArgBase::Tag tag;
int number;
const void *data;
qsizetype size;
};
} // unnamed namespace
template <>
class QTypeInfo<Part> : public QTypeInfoMerger<Part, QStringView, int> {}; // Q_DECLARE_METATYPE
Q_DECLARE_TYPEINFO(Part, Q_PRIMITIVE_TYPE);
namespace {
@ -8837,7 +8846,8 @@ enum { ExpectedParts = 32 };
typedef QVarLengthArray<Part, ExpectedParts> ParseResult;
typedef QVarLengthArray<int, ExpectedParts/2> ArgIndexToPlaceholderMap;
static ParseResult parseMultiArgFormatString(QStringView s)
template <typename StringView>
static ParseResult parseMultiArgFormatString(StringView s)
{
ParseResult result;
@ -8884,16 +8894,29 @@ static ArgIndexToPlaceholderMap makeArgIndexToPlaceholderMap(const ParseResult &
return result;
}
static qsizetype resolveStringRefsAndReturnTotalSize(ParseResult &parts, const ArgIndexToPlaceholderMap &argIndexToPlaceholderMap, const QString *args[])
static qsizetype resolveStringRefsAndReturnTotalSize(ParseResult &parts, const ArgIndexToPlaceholderMap &argIndexToPlaceholderMap, const QtPrivate::ArgBase *args[])
{
using namespace QtPrivate;
qsizetype totalSize = 0;
for (Part &part : parts) {
if (part.number != -1) {
const auto it = std::find(argIndexToPlaceholderMap.begin(), argIndexToPlaceholderMap.end(), part.number);
if (it != argIndexToPlaceholderMap.end())
part.string = *args[it - argIndexToPlaceholderMap.begin()];
if (it != argIndexToPlaceholderMap.end()) {
const auto &arg = *args[it - argIndexToPlaceholderMap.begin()];
switch (arg.tag) {
case ArgBase::L1:
part.reset(static_cast<const QLatin1StringArg&>(arg).string);
break;
case ArgBase::U8:
Q_UNREACHABLE(); // waiting for QUtf8String...
break;
case ArgBase::U16:
part.reset(static_cast<const QStringViewArg&>(arg).string);
break;
}
}
}
totalSize += part.string.size();
totalSize += part.size;
}
return totalSize;
}
@ -8901,18 +8924,35 @@ static qsizetype resolveStringRefsAndReturnTotalSize(ParseResult &parts, const A
} // unnamed namespace
QString QString::multiArg(int numArgs, const QString **args) const
{
QVarLengthArray<QtPrivate::QStringViewArg, 9> sva;
sva.reserve(numArgs);
QVarLengthArray<const QtPrivate::ArgBase *, 9> pointers;
pointers.reserve(numArgs);
for (int i = 0; i < numArgs; ++i) {
sva.push_back(QtPrivate::qStringLikeToArg(*args[i]));
pointers.push_back(&sva.back());
}
return QtPrivate::argToQString(qToStringViewIgnoringNull(*this), static_cast<size_t>(numArgs), pointers.data());
}
Q_ALWAYS_INLINE QString to_string(QLatin1String s) noexcept { return s; }
Q_ALWAYS_INLINE QString to_string(QStringView s) noexcept { return s.toString(); }
template <typename StringView>
static QString argToQStringImpl(StringView pattern, size_t numArgs, const QtPrivate::ArgBase **args)
{
// Step 1-2 above
ParseResult parts = parseMultiArgFormatString(qToStringViewIgnoringNull(*this));
ParseResult parts = parseMultiArgFormatString(pattern);
// 3-4
ArgIndexToPlaceholderMap argIndexToPlaceholderMap = makeArgIndexToPlaceholderMap(parts);
if (argIndexToPlaceholderMap.size() > numArgs) // 3a
argIndexToPlaceholderMap.resize(numArgs);
else if (argIndexToPlaceholderMap.size() < numArgs) // 3b
qWarning("QString::arg: %d argument(s) missing in %s",
numArgs - argIndexToPlaceholderMap.size(), toLocal8Bit().data());
if (static_cast<size_t>(argIndexToPlaceholderMap.size()) > numArgs) // 3a
argIndexToPlaceholderMap.resize(int(numArgs));
else if (Q_UNLIKELY(static_cast<size_t>(argIndexToPlaceholderMap.size()) < numArgs)) // 3b
qWarning("QString::arg: %d argument(s) missing in %ls",
int(numArgs - argIndexToPlaceholderMap.size()), qUtf16Printable(to_string(pattern)));
// 5
const qsizetype totalSize = resolveStringRefsAndReturnTotalSize(parts, argIndexToPlaceholderMap, args);
@ -8922,15 +8962,37 @@ QString QString::multiArg(int numArgs, const QString **args) const
auto out = const_cast<QChar*>(result.constData());
for (Part part : parts) {
if (const qsizetype sz = part.string.size()) {
memcpy(out, part.string.data(), sz * sizeof(QChar));
out += sz;
switch (part.tag) {
case QtPrivate::ArgBase::L1:
if (part.size) {
qt_from_latin1(reinterpret_cast<ushort*>(out),
reinterpret_cast<const char*>(part.data), part.size);
}
break;
case QtPrivate::ArgBase::U8:
Q_UNREACHABLE(); // waiting for QUtf8String
break;
case QtPrivate::ArgBase::U16:
if (part.size)
memcpy(out, part.data, part.size * sizeof(QChar));
break;
}
out += part.size;
}
return result;
}
QString QtPrivate::argToQString(QStringView pattern, size_t n, const ArgBase **args)
{
return argToQStringImpl(pattern, n, args);
}
QString QtPrivate::argToQString(QLatin1String pattern, size_t n, const ArgBase **args)
{
return argToQStringImpl(pattern, n, args);
}
/*! \fn bool QString::isSimpleText() const
\internal

View File

@ -106,6 +106,9 @@ public:
Q_DECL_CONSTEXPR bool isNull() const noexcept { return !data(); }
Q_DECL_CONSTEXPR bool isEmpty() const noexcept { return !size(); }
template <typename...Args>
Q_REQUIRED_RESULT inline QString arg(Args &&...args) const;
Q_DECL_CONSTEXPR QLatin1Char at(int i) const
{ return Q_ASSERT(i >= 0), Q_ASSERT(i < size()), QLatin1Char(m_data[i]); }
Q_DECL_CONSTEXPR QLatin1Char operator[](int i) const { return at(i); }
@ -1979,6 +1982,58 @@ inline const QString &asString(const QString &s) { return s; }
inline QString &&asString(QString &&s) { return std::move(s); }
}
//
// QStringView::arg() implementation
//
namespace QtPrivate {
struct ArgBase {
enum Tag : uchar { L1, U8, U16 } tag;
};
struct QStringViewArg : ArgBase {
QStringView string;
QStringViewArg() = default;
Q_DECL_CONSTEXPR explicit QStringViewArg(QStringView v) noexcept : ArgBase{U16}, string{v} {}
};
struct QLatin1StringArg : ArgBase {
QLatin1String string;
QLatin1StringArg() = default;
Q_DECL_CONSTEXPR explicit QLatin1StringArg(QLatin1String v) noexcept : ArgBase{L1}, string{v} {}
};
Q_REQUIRED_RESULT Q_CORE_EXPORT QString argToQString(QStringView pattern, size_t n, const ArgBase **args);
Q_REQUIRED_RESULT Q_CORE_EXPORT QString argToQString(QLatin1String pattern, size_t n, const ArgBase **args);
template <typename StringView, typename...Args>
Q_REQUIRED_RESULT Q_ALWAYS_INLINE QString argToQStringDispatch(StringView pattern, const Args &...args)
{
const ArgBase *argBases[] = {&args..., /* avoid zero-sized array */ nullptr};
return QtPrivate::argToQString(pattern, sizeof...(Args), argBases);
}
Q_DECL_CONSTEXPR inline QStringViewArg qStringLikeToArg(QStringView s) noexcept { return QStringViewArg{s}; }
inline QStringViewArg qStringLikeToArg(const QChar &c) noexcept { return QStringViewArg{QStringView{&c, 1}}; }
Q_DECL_CONSTEXPR inline QLatin1StringArg qStringLikeToArg(QLatin1String s) noexcept { return QLatin1StringArg{s}; }
} // namespace QtPrivate
template <typename...Args>
Q_ALWAYS_INLINE
QString QStringView::arg(Args &&...args) const
{
return QtPrivate::argToQStringDispatch(*this, QtPrivate::qStringLikeToArg(args)...);
}
template <typename...Args>
Q_ALWAYS_INLINE
QString QLatin1String::arg(Args &&...args) const
{
return QtPrivate::argToQStringDispatch(*this, QtPrivate::qStringLikeToArg(args)...);
}
QT_END_NAMESPACE
#if defined(QT_USE_FAST_OPERATOR_PLUS) || defined(QT_USE_QSTRINGBUILDER)

View File

@ -529,6 +529,24 @@ QT_BEGIN_NAMESPACE
\sa operator[](), front(), back()
*/
/*!
\fn QString QStringView::arg(Args &&...args) const
\fn QString QLatin1String::arg(Args &&...args) const
\since 5.14
Replaces occurrences of \c{%N} in this string with the corresponding
argument from \a args. The arguments are not positional: the first of
the \a args replaces the \c{%N} with the lowest \c{N} (all of them), the
second of the \a args the \c{%N} with the next-lowest \c{N} etc.
\c Args can consist of anything that implicitly converts to QStringView
or QLatin1String.
In addition, the following types are also supported: QChar, QLatin1Char.
\sa QString::arg()
*/
/*!
\fn QChar QStringView::front() const

View File

@ -226,6 +226,9 @@ public:
// QString API
//
template <typename...Args>
Q_REQUIRED_RESULT inline QString arg(Args &&...args) const; // defined in qstring.h
Q_REQUIRED_RESULT QByteArray toLatin1() const { return QtPrivate::convertToLatin1(*this); }
Q_REQUIRED_RESULT QByteArray toUtf8() const { return QtPrivate::convertToUtf8(*this); }
Q_REQUIRED_RESULT QByteArray toLocal8Bit() const { return QtPrivate::convertToLocal8Bit(*this); }

View File

@ -45,6 +45,7 @@ class tst_QLatin1String : public QObject
private Q_SLOTS:
void at();
void arg() const;
void midLeftRight();
void nullString();
void emptyString();
@ -63,6 +64,47 @@ void tst_QLatin1String::at()
QCOMPARE(l1[l1.size() - 1], QLatin1Char('d'));
}
void tst_QLatin1String::arg() const
{
#define CHECK1(pattern, arg1, expected) \
do { \
auto p = QLatin1String(pattern); \
QCOMPARE(p.arg(QLatin1String(arg1)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1)), expected); \
QCOMPARE(p.arg(QStringLiteral(arg1)), expected); \
QCOMPARE(p.arg(QString(QLatin1String(arg1))), expected); \
} while (false) \
/*end*/
#define CHECK2(pattern, arg1, arg2, expected) \
do { \
auto p = QLatin1String(pattern); \
QCOMPARE(p.arg(QLatin1String(arg1), QLatin1String(arg2)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1), QLatin1String(arg2)), expected); \
QCOMPARE(p.arg(QLatin1String(arg1), QStringViewLiteral(arg2)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1), QStringViewLiteral(arg2)), expected); \
} while (false) \
/*end*/
CHECK1("", "World", "");
CHECK1("%1", "World", "World");
CHECK1("!%1?", "World", "!World?");
CHECK1("%1%1", "World", "WorldWorld");
CHECK1("%1%2", "World", "World%2");
CHECK1("%2%1", "World", "%2World");
CHECK2("", "Hello", "World", "");
CHECK2("%1", "Hello", "World", "Hello");
CHECK2("!%1, %2?", "Hello", "World", "!Hello, World?");
CHECK2("%1%1", "Hello", "World", "HelloHello");
CHECK2("%1%2", "Hello", "World", "HelloWorld");
CHECK2("%2%1", "Hello", "World", "WorldHello");
#undef CHECK2
#undef CHECK1
QCOMPARE(QLatin1String(" %2 %2 %1 %3 ").arg(QLatin1Char('c'), QChar::CarriageReturn, u'C'), " \r \r c C ");
}
void tst_QLatin1String::midLeftRight()
{
const QLatin1String l1("Hello World");

View File

@ -134,6 +134,8 @@ private Q_SLOTS:
void literals() const;
void at() const;
void arg() const;
void fromQString() const;
void fromQStringRef() const;
@ -425,6 +427,47 @@ void tst_QStringView::at() const
QCOMPARE(sv.at(4), QChar('o')); QCOMPARE(sv[4], QChar('o'));
}
void tst_QStringView::arg() const
{
#define CHECK1(pattern, arg1, expected) \
do { \
auto p = QStringViewLiteral(pattern); \
QCOMPARE(p.arg(QLatin1String(arg1)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1)), expected); \
QCOMPARE(p.arg(QStringLiteral(arg1)), expected); \
QCOMPARE(p.arg(QString(QLatin1String(arg1))), expected); \
} while (false) \
/*end*/
#define CHECK2(pattern, arg1, arg2, expected) \
do { \
auto p = QStringViewLiteral(pattern); \
QCOMPARE(p.arg(QLatin1String(arg1), QLatin1String(arg2)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1), QLatin1String(arg2)), expected); \
QCOMPARE(p.arg(QLatin1String(arg1), QStringViewLiteral(arg2)), expected); \
QCOMPARE(p.arg(QStringViewLiteral(arg1), QStringViewLiteral(arg2)), expected); \
} while (false) \
/*end*/
CHECK1("", "World", "");
CHECK1("%1", "World", "World");
CHECK1("!%1?", "World", "!World?");
CHECK1("%1%1", "World", "WorldWorld");
CHECK1("%1%2", "World", "World%2");
CHECK1("%2%1", "World", "%2World");
CHECK2("", "Hello", "World", "");
CHECK2("%1", "Hello", "World", "Hello");
CHECK2("!%1, %2?", "Hello", "World", "!Hello, World?");
CHECK2("%1%1", "Hello", "World", "HelloHello");
CHECK2("%1%2", "Hello", "World", "HelloWorld");
CHECK2("%2%1", "Hello", "World", "WorldHello");
#undef CHECK2
#undef CHECK1
QCOMPARE(QStringViewLiteral(" %2 %2 %1 %3 ").arg(QLatin1Char('c'), QChar::CarriageReturn, u'C'), " \r \r c C ");
}
void tst_QStringView::fromQString() const
{
QString null;