QStringBuilder: allow to be used with 'auto' keyword

The idea is to store a concatenable in a QStringBuilder object by value
or by reference, depending on how it was originally passed into the
concatenation operator. So if it was passed by r-value, we treat it as
a temporary object and hold it by value (and use move-semantic if
available), otherwise we hold it by reference (as before).

To achieve this we first change concatenation operators '%' and '+'
to take their arguments by universal reference. Next we instantiate
QStringBuilder object with deduced types of the arguments, which will
be a "value type" or a "reference type" according to "universal
reference deduction rules".

Further we use perfect forwarding to pass arguments to QStringBuilder's
constructor. Thus arguments, initially passed by r-value reference
and which are move-constructible, will be "moved" to corresponding
QStringBuilder member variables.

So, to summarize:
1. Arguments passed by l-value reference - stored in QStringBuilder
   object by reference (as before).
2. Temporary objects passed by r-value reference - stored in
   QStringBuilder object by value. If a type is move-constructible
   (QSting, QByteArray, etc), the object will be "moved" accordingly.

Special thanks to Giuseppe D'Angelo for the tests.

Fixes: QTBUG-99291
Fixes: QTBUG-87603
Fixes: QTBUG-47066
Task-number: QTBUG-74873
Task-number: QTBUG-103090
Task-number: QTBUG-104354
Change-Id: I64f417be0de0815ec5ae7e35a1cc6cef6d887933
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Vladimir Belyavsky <belyavskyv@gmail.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Vladimir Belyavsky 2023-05-19 14:58:50 +03:00
parent 8d1304f4f2
commit af8f9a2a6e
2 changed files with 187 additions and 18 deletions

View File

@ -30,7 +30,10 @@ protected:
static void appendLatin1To(QLatin1StringView in, QChar *out) noexcept;
};
template <typename T> struct QConcatenable {};
template <typename T> struct QConcatenable;
template <typename T>
using QConcatenableEx = QConcatenable<q20::remove_cvref_t<T>>;
namespace QtStringBuilder {
template <typename A, typename B> struct ConvertToTypeHelper
@ -72,10 +75,20 @@ struct QStringBuilderBase<Builder, QString> : public QStringBuilderCommon<Builde
};
template <typename A, typename B>
class QStringBuilder : public QStringBuilderBase<QStringBuilder<A, B>, typename QtStringBuilder::ConvertToTypeHelper<typename QConcatenable<A>::ConvertTo, typename QConcatenable<B>::ConvertTo>::ConvertTo>
class QStringBuilder : public QStringBuilderBase<QStringBuilder<A, B>,
typename QtStringBuilder::ConvertToTypeHelper<
typename QConcatenableEx<A>::ConvertTo,
typename QConcatenableEx<B>::ConvertTo
>::ConvertTo
>
{
public:
QStringBuilder(const A &a_, const B &b_) : a(a_), b(b_) {}
QStringBuilder(A &&a_, B &&b_) : a(std::forward<A>(a_)), b(std::forward<B>(b_)) {}
QStringBuilder(QStringBuilder &&) = default;
QStringBuilder(const QStringBuilder &) = default;
~QStringBuilder() = default;
private:
friend class QByteArray;
friend class QString;
@ -116,8 +129,12 @@ public:
return QtStringBuilder::isNull(a) && QtStringBuilder::isNull(b);
}
const A &a;
const B &b;
A a;
B b;
private:
QStringBuilder &operator=(QStringBuilder &&) = delete;
QStringBuilder &operator=(const QStringBuilder &) = delete;
};
template <> struct QConcatenable<char> : private QAbstractConcatenable
@ -361,34 +378,37 @@ template <typename A, typename B>
struct QConcatenable< QStringBuilder<A, B> >
{
typedef QStringBuilder<A, B> type;
typedef typename QtStringBuilder::ConvertToTypeHelper<typename QConcatenable<A>::ConvertTo, typename QConcatenable<B>::ConvertTo>::ConvertTo ConvertTo;
enum { ExactSize = QConcatenable<A>::ExactSize && QConcatenable<B>::ExactSize };
using ConvertTo = typename QtStringBuilder::ConvertToTypeHelper<
typename QConcatenableEx<A>::ConvertTo,
typename QConcatenableEx<B>::ConvertTo
>::ConvertTo;
enum { ExactSize = QConcatenableEx<A>::ExactSize && QConcatenableEx<B>::ExactSize };
static qsizetype size(const type &p)
{
return QConcatenable<A>::size(p.a) + QConcatenable<B>::size(p.b);
return QConcatenableEx<A>::size(p.a) + QConcatenableEx<B>::size(p.b);
}
template<typename T> static inline void appendTo(const type &p, T *&out)
{
QConcatenable<A>::appendTo(p.a, out);
QConcatenable<B>::appendTo(p.b, out);
QConcatenableEx<A>::appendTo(p.a, out);
QConcatenableEx<B>::appendTo(p.b, out);
}
};
template <typename A, typename B>
QStringBuilder<typename QConcatenable<A>::type, typename QConcatenable<B>::type>
operator%(const A &a, const B &b)
template <typename A, typename B,
typename = std::void_t<typename QConcatenableEx<A>::type, typename QConcatenableEx<B>::type>>
auto operator%(A &&a, B &&b)
{
return QStringBuilder<typename QConcatenable<A>::type, typename QConcatenable<B>::type>(a, b);
return QStringBuilder<A, B>(std::forward<A>(a), std::forward<B>(b));
}
// QT_USE_FAST_OPERATOR_PLUS was introduced in 4.7, QT_USE_QSTRINGBUILDER is to be used from 4.8 onwards
// QT_USE_FAST_OPERATOR_PLUS does not remove the normal operator+ for QByteArray
#if defined(QT_USE_FAST_OPERATOR_PLUS) || defined(QT_USE_QSTRINGBUILDER)
template <typename A, typename B>
QStringBuilder<typename QConcatenable<A>::type, typename QConcatenable<B>::type>
operator+(const A &a, const B &b)
template <typename A, typename B,
typename = std::void_t<typename QConcatenableEx<A>::type, typename QConcatenableEx<B>::type>>
auto operator+(A &&a, B &&b)
{
return QStringBuilder<typename QConcatenable<A>::type, typename QConcatenable<B>::type>(a, b);
return std::forward<A>(a) % std::forward<B>(b);
}
#endif

View File

@ -98,6 +98,152 @@ void checkNullVsEmpty(const String &empty)
QVERIFY(result.isNull());
}
namespace CheckAuto {
// T is cvref-qualified, using universal reference deduction rules.
template <typename T> struct Helper;
// These specializations forward to the non-const ones, and add const on top.
template <typename T> struct Helper<const T>
{
static const T create() { return Helper<T>::create(); }
static const T createNull() { return Helper<T>::createNull(); }
};
template <typename T> struct Helper<const T &>
{
static const T &create() { return Helper<T &>::create(); }
static const T &createNull() { return Helper<T &>::createNull(); }
};
template <> struct Helper<QString>
{
static QString create() { return QString::fromUtf8("QString rvalue"); }
static QString createNull() { return QString(); }
};
template <> struct Helper<QString &>
{
static QString &create() { static QString s = QString::fromUtf8("QString lvalue"); return s; }
static QString &createNull() { static QString s; return s; }
};
template <> struct Helper<QStringView>
{
static QStringView create() { return QStringView(u"QStringView rvalue"); }
static QStringView createNull() { return QStringView(); }
};
template <> struct Helper<QStringView &>
{
static QStringView &create() { static QStringView s = u"QStringView lvalue"; return s; }
static QStringView &createNull() { static QStringView s; return s; }
};
template <> struct Helper<QByteArray>
{
static QByteArray create() { return QByteArray("QByteArray rvalue"); }
static QByteArray createNull() { return QByteArray(); }
};
template <> struct Helper<QByteArray &>
{
static QByteArray &create() { static QByteArray ba = QByteArray("QByteArray lvalue"); return ba; }
static QByteArray &createNull() { static QByteArray ba; return ba; }
};
template <> struct Helper<QByteArrayView>
{
static QByteArrayView create() { return QByteArrayView("QByteArrayView rvalue"); }
static QByteArrayView createNull() { return QByteArrayView(); }
};
template <> struct Helper<QByteArrayView &>
{
static QByteArrayView &create() { static QByteArrayView ba = "QByteArrayView lvalue"; return ba; }
static QByteArrayView &createNull() { static QByteArrayView ba; return ba; }
};
template <> struct Helper<const char *>
{
static const char *create() { return "const char * rvalue"; }
static const char *createNull() { return ""; }
};
template <> struct Helper<const char *&>
{
static const char *&create() { static const char *s = "const char * lvalue"; return s; }
static const char *&createNull() { static const char *s = ""; return s; }
};
template <typename String1, typename String2, typename Result>
void checkAutoImpl3()
{
{
auto result = Helper<String1>::create() P Helper<String2>::create();
Result expected = result;
QCOMPARE(result, expected);
}
{
auto result = Helper<String2>::create() P Helper<String1>::create();
Result expected = result;
QCOMPARE(result, expected);
}
{
auto result = Helper<String1>::create() P Helper<String2>::create() P Helper<String1>::create();
Result expected = result;
QCOMPARE(result, expected);
}
{
auto result = Helper<String2>::create() P Helper<String1>::create() P Helper<String2>::create();
Result expected = result;
QCOMPARE(result, expected);
}
{
auto result = Helper<String1>::createNull() P Helper<String2>::create();
Result expected = result;
QCOMPARE(result, expected);
}
{
auto result = Helper<String1>::createNull() P Helper<String2>::createNull();
Result expected = result;
QCOMPARE(result, expected);
}
}
template <typename String1, typename String2, typename Result>
void checkAutoImpl2()
{
checkAutoImpl3<String1 , String2 , Result>();
checkAutoImpl3<String1 &, String2 , Result>();
checkAutoImpl3<String1 , String2 &, Result>();
checkAutoImpl3<String1 &, String2 &, Result>();
}
template <typename String1, typename String2, typename Result>
void checkAutoImpl()
{
checkAutoImpl2< String1, String2, Result>();
checkAutoImpl2<const String1, String2, Result>();
checkAutoImpl2< String1, const String2, Result>();
checkAutoImpl2<const String1, const String2, Result>();
}
} // namespace CheckAuto
void checkAuto()
{
CheckAuto::checkAutoImpl<QString, QString, QString>();
CheckAuto::checkAutoImpl<QString, QStringView, QString>();
CheckAuto::checkAutoImpl<QByteArray, QByteArray, QByteArray>();
CheckAuto::checkAutoImpl<QByteArray, const char *, QByteArray>();
CheckAuto::checkAutoImpl<QByteArray, QByteArrayView, QByteArray>();
#ifndef QT_NO_CAST_FROM_ASCII
CheckAuto::checkAutoImpl<QString, const char *, QString>();
CheckAuto::checkAutoImpl<QString, QByteArray, QString>();
#endif
}
void runScenario()
{
// this code is latin1. TODO: replace it with the utf8 block below, once
@ -381,6 +527,9 @@ void runScenario()
checkNullVsEmpty(QStringLiteral(""));
checkNullVsEmpty(QByteArrayLiteral(""));
// auto
checkAuto();
checkItWorksWithFreeSpaceAtBegin(QByteArray(UTF8_LITERAL), "1234");
if (QTest::currentTestFailed())
return;