diff --git a/src/corelib/tools/qalgorithms.h b/src/corelib/tools/qalgorithms.h index 8889effecee..44b9f030814 100644 --- a/src/corelib/tools/qalgorithms.h +++ b/src/corelib/tools/qalgorithms.h @@ -9,6 +9,7 @@ #endif #include +#include #if __has_include() && __cplusplus > 201703L #include @@ -435,6 +436,25 @@ QT_POPCOUNT_RELAXED_CONSTEXPR inline uint qCountLeadingZeroBits(unsigned long v) #undef QT_POPCOUNT_RELAXED_CONSTEXPR +template +Result qJoin(InputIterator first, InputIterator last, Result init, const Separator &separator = {}, + Projection p = {}) +{ + if (first != last) { + init += std::invoke(p, *first); + ++first; + } + + while (first != last) { + init += separator; + init += std::invoke(p, *first); + ++first; + } + + return init; +} + QT_END_NAMESPACE #endif // QALGORITHMS_H diff --git a/src/corelib/tools/qalgorithms.qdoc b/src/corelib/tools/qalgorithms.qdoc index 5fe0b3e8c3a..1a9e8d9a619 100644 --- a/src/corelib/tools/qalgorithms.qdoc +++ b/src/corelib/tools/qalgorithms.qdoc @@ -240,3 +240,24 @@ For example, qCountLeadingZeroBits(quint64(1)) returns 63 and qCountLeadingZeroBits(quint64(8)) returns 60. */ + +/*! + \fn template Result qJoin(InputIterator first, InputIterator last, Result init, const Separator &separator, Projection p) + \relates + \since 6.10 + + Starts with initial value \a init and cumulatively adds to it each entry in + the range from \a first to \a last. Each entry is, before adding, mapped + through the projection \a p (which defaults to the identity). Between each + entry and the next, an optional \a separator is added. For example: + + \code + QList l = {1, 2, 3}; + QString res = qJoin(l.cbegin(), l.cend(), QString(), u" / ", + [](int n) { return QString::number(2 * n);}); + // res == "2 / 4 / 6" + \endcode + + \note \c{q20::identity} is a C++17 backport of C++20's + \l{https://en.cppreference.com/w/cpp/utility/functional/identity}{\c{std::identity}}. +*/ diff --git a/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp b/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp index 8d68a7a2706..c282b836c12 100644 --- a/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp +++ b/tests/auto/corelib/tools/qalgorithms/tst_qalgorithms.cpp @@ -13,13 +13,30 @@ QT_WARNING_DISABLE_DEPRECATED #include #include #include +#include #include +#include #include #include +#include #define Q_TEST_PERFORMANCE 0 using namespace std; +using namespace Qt::StringLiterals; + +template +Container make(int size, T factor) +{ + Container c; + c.reserve(size); + using V = typename Container::value_type; + int i = 0; + std::generate_n(std::inserter(c, c.end()), size, [factor, &i] { return V(++i * factor); }); + return c; +} + +constexpr auto QString_number = [](auto v) { return QString::number(v); }; class tst_QAlgorithms : public QObject { @@ -56,6 +73,71 @@ private slots: void countLeading32() { countLeading_impl(); } void countLeading64() { countLeading_impl(); } + void join_QList_int() + { join_impl(make>(3, 10), u" / ", u"10 / 20 / 30"_s, QString_number); } + void join_QList_float() + { join_impl(make>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s, QString_number); } + void join_QList_char() + { join_impl(make>(3, 33), u" / ", u"! / B / c"_s); } + void join_QList_QChar() + { + join_impl(make>(3, 33), " / ", std::string("! / B / c"), + [](auto v) { return v.toLatin1(); }); + } + void join_QList_pair() + { + join_impl(QList>{{u"one"_s, 1}, {u"two"_s, 2}}, u'/' , + u"one/two"_s, [](const std::pair& p) { return p.first; }); + } + + void join_QVarLengthArray_int() + { + join_impl(make>(3, 10), " / ", std::string("10 / 20 / 30"), + [](auto v) { return std::to_string(v); }); + } + void join_QVarLengthArray_float() + { + join_impl(make>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s, + QString_number); + } + + void join_std_vector_int() + { join_impl(make>(3, 10), u" / ", u"10 / 20 / 30"_s, QString_number); } + void join_std_vector_float() + { + join_impl(make>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s, + QString_number); + } + + void join_std_array_int() + { join_impl(std::array {10, 20, 30}, u" / ", u"10 / 20 / 30"_s, QString_number); } + void join_std_array_float() + { + join_impl(std::array {10.1f, 20.2f, 30.3f}, u" / ", u"10.1 / 20.2 / 30.3"_s, + QString_number); + } + + void join_QMap_int() + { join_impl(QMap{{u"one"_s, 1}, {u"two"_s, 2}}, ' ', u"1 2"_s, QString_number); } + void join_QMap_float() + { + join_impl(QMap{{u"one"_s, 1.1f}, {u"two"_s, 2.2f}}, ' ', u"1.1 2.2"_s, + QString_number); + } + + void join_QSet_int() + { join_impl_qset(make>(3, 10), u"/"_s, QString_number); } + void join_QSet_float() + { join_impl_qset(make>(3, 10.1f), u" - "_s, QString_number); } + + void join_InputIterator_words() + { + join_impl_InputIterator(std::stringstream("10 20 30"), u" / ", u"10 / 20 / 30"_s, + QString_number); + } + void join_InputIterator_single() + { join_impl_InputIterator(std::stringstream("123"), u'-', u"123"_s, QString_number); } + private: void popCount_data_impl(size_t sizeof_T_Int); template @@ -68,6 +150,16 @@ private: void countLeading_data_impl(size_t sizeof_T_Int); template void countLeading_impl(); + + template + void join_impl(const Container &input, const Separator &sep, const Result &expectedResult, + Projection p = {}); + template + void join_impl_qset(const QSet &input, const Separator &sep, Projection p); + template + void join_impl_InputIterator(std::stringstream ss, const Separator &sep, + const Result &expectedResult, Projection p); }; template struct PrintIfFailed @@ -373,6 +465,40 @@ void tst_QAlgorithms::countLeading_impl() QCOMPARE(qCountLeadingZeroBits(value), expected); } +template +void tst_QAlgorithms::join_impl(const Container &input, const Separator &sep, + const Result &expectedResult, Projection p) +{ + const Result res = qJoin(input.cbegin(), input.cend(), Result{}, sep, p); + QCOMPARE(res, expectedResult); +} + +template +void tst_QAlgorithms::join_impl_qset(const QSet &input, const Separator &sep, Projection p) +{ + // Because a set isn't ordered, check againt an alternative implementation of qJoin() + QString res = qJoin(input.cbegin(), input.cend(), QString(), sep, p); + + QString expected; + for (const auto &e : input) { + expected += p(e); + expected += sep; + } + + if (!expected.isEmpty()) + expected.chop(sep.size()); + QCOMPARE(res, expected); +} + +template +void tst_QAlgorithms::join_impl_InputIterator(std::stringstream ss, const Separator &sep, + const Result &expectedResult, Projection p) +{ + const Result res = qJoin(std::istream_iterator{ss}, std::istream_iterator{}, Result{}, + sep, p); + QCOMPARE(res, expectedResult); +} + QTEST_APPLESS_MAIN(tst_QAlgorithms) #include "tst_qalgorithms.moc"