diff --git a/autotests/shared/akrangestest.cpp b/autotests/shared/akrangestest.cpp index 3d3424b50..36242f9d5 100644 --- a/autotests/shared/akrangestest.cpp +++ b/autotests/shared/akrangestest.cpp @@ -1,372 +1,386 @@ /* Copyright (c) 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include using namespace Akonadi; namespace { int transformFreeFunc(int i) { return i * 2; } struct TransformHelper { public: static int transform(int i) { return transformFreeFunc(i); } int operator()(int i) const { return transformFreeFunc(i); } }; bool filterFreeFunc(int i) { return i % 2 == 0; } struct FilterHelper { public: static bool filter(int i) { return filterFreeFunc(i); } bool operator()(int i) { return filterFreeFunc(i); } }; } class AkRangesTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qSetGlobalQHashSeed(0); } void testContainerConversion() { { QVector in = { 1, 2, 3, 4, 5 }; QCOMPARE(in | Akonadi::toQList, in.toList()); QCOMPARE(in | Akonadi::toQList | Akonadi::toQVector, in); QCOMPARE(in | Akonadi::toQSet, in.toList().toSet()); } { QList in = { 1, 2, 3, 4, 5 }; QCOMPARE(in | Akonadi::toQVector, in.toVector()); QCOMPARE(in | Akonadi::toQVector | toQList, in); QCOMPARE(in | Akonadi::toQSet, in.toSet()); } } void testRangeConversion() { { QList in = { 1, 2, 3, 4, 5 }; Akonadi::detail::Range::const_iterator> range(in.cbegin(), in.cend()); QCOMPARE(in | Akonadi::toQVector, QVector::fromList(in)); } { QVector in = { 1, 2, 3, 4, 5 }; Akonadi::detail::Range::const_iterator> range(in.cbegin(), in.cend()); QCOMPARE(in | toQList, in.toList()); } } void testTransform() { QList in = { 1, 2, 3, 4, 5 }; QList out = { 2, 4, 6, 8, 10 }; QCOMPARE(in | transform([](int i) { return i * 2; }) | toQList, out); QCOMPARE(in | transform(transformFreeFunc) | toQList, out); QCOMPARE(in | transform(&TransformHelper::transform) | toQList, out); QCOMPARE(in | transform(TransformHelper()) | toQList, out); } private: class CopyCounter { public: CopyCounter() = default; CopyCounter(const CopyCounter &other) : copyCount(other.copyCount + 1), transformed(other.transformed) {} CopyCounter(CopyCounter &&other) = default; CopyCounter &operator=(const CopyCounter &other) { copyCount = other.copyCount + 1; transformed = other.transformed; return *this; } CopyCounter &operator=(CopyCounter &&other) = default; int copyCount = 0; bool transformed = false; }; private Q_SLOTS: void testTransformCopyCount() { { QList in = { {} }; // 1st copy (QList::append()) QList out = in | transform([](const auto &c) { CopyCounter r(c); // 2nd copy (expected) r.transformed = true; return r; }) | toQList; // 3rd copy (QList::append()) QCOMPARE(out.size(), in.size()); QCOMPARE(out[0].copyCount, 3); QCOMPARE(out[0].transformed, true); } { QVector in(1); // construct vector of one element, so no copying // occurs at initialization QVector out = in | transform([](const auto &c) { CopyCounter r(c); // 1st copy r.transformed = true; return r; }) | toQVector; QCOMPARE(out.size(), in.size()); QCOMPARE(out[0].copyCount, 1); QCOMPARE(out[0].transformed, true); } } void testTransformConvert() { { QList in = { 1, 2, 3, 4, 5 }; QVector out = { 2, 4, 6, 8, 10 }; QCOMPARE(in | transform([](int i) { return i * 2; }) | toQVector, out); } { QVector in = { 1, 2, 3, 4, 5 }; QList out = { 2, 4, 6, 8, 10 }; QCOMPARE(in | transform([](int i) { return i * 2; }) | toQList, out); } } void testCreateRange() { { QList in = { 1, 2, 3, 4, 5, 6 }; QList out = { 3, 4, 5 }; QCOMPARE(range(in.begin() + 2, in.begin() + 5) | toQList, out); } } void testRangeWithTransform() { { QList in = { 1, 2, 3, 4, 5, 6 }; QList out = { 6, 8, 10 }; QCOMPARE(range(in.begin() + 2, in.begin() + 5) | transform([](int i) { return i * 2; }) | toQList, out); } } void testTransformType() { { QStringList in = { QStringLiteral("foo"), QStringLiteral("foobar"), QStringLiteral("foob") }; QList out = { 3, 6, 4 }; QCOMPARE(in | transform([](const auto &str) { return str.size(); }) | toQList, out); } } void testFilter() { { QList in = { 1, 2, 3, 4, 5, 6, 7, 8 }; QList out = { 2, 4, 6, 8 }; QCOMPARE(in | filter([](int i) { return i % 2 == 0; }) | toQList, out); QCOMPARE(in | filter(filterFreeFunc) | toQList, out); QCOMPARE(in | filter(&FilterHelper::filter) | toQList, out); QCOMPARE(in | filter(FilterHelper()) | toQList, out); } } void testFilterTransform() { { QStringList in = { QStringLiteral("foo"), QStringLiteral("foobar"), QStringLiteral("foob") }; QList out = { 6 }; QCOMPARE(in | transform(&QString::size) | filter([](int i) { return i > 5; }) | toQList, out); QCOMPARE(in | filter([](const auto &str) { return str.size() > 5; }) | transform(&QString::size) | toQList, out); } } void testTemporaryContainer() { const auto func = []{ QStringList rv; for (int i = 0; i < 5; i++) { rv.push_back(QString::number(i)); } return rv; }; { QList out = { 0, 2, 4 }; QCOMPARE(func() | transform([](const auto &str) { return str.toInt(); }) | filter([](int i) { return i % 2 == 0; }) | toQList, out); } { QList out = { 0, 2, 4 }; QCOMPARE(func() | filter([](const auto &v) { return v.toInt() % 2 == 0; }) | transform([](const auto &str) { return str.toInt(); }) | toQList, out); } } void testTemporaryRange() { const auto func = []{ QStringList rv; for (int i = 0; i < 5; ++i) { rv.push_back(QString::number(i)); } return rv | transform([](const auto &str) { return str.toInt(); }); }; QList out = { 1, 3 }; QCOMPARE(func() | filter([](int i) { return i % 2 == 1; }) | toQList, out); } private: struct ForEachCallable { public: ForEachCallable(QList &out) : mOut(out) {} void operator()(int i) { mOut.push_back(i); } static void append(int i) { sOut.push_back(i); } static void clear() { sOut.clear(); } static QList sOut; private: QList &mOut; }; private Q_SLOTS: void testForEach() { const QList in = { 1, 2, 3, 4, 5, 6 }; { QList out; in | forEach([&out](int v) { out.push_back(v); }); QCOMPARE(out, in); } { QList out; in | forEach(ForEachCallable(out)); QCOMPARE(out, in); } { ForEachCallable::clear(); in | forEach(&ForEachCallable::append); QCOMPARE(ForEachCallable::sOut, in); } { QList out; QCOMPARE(in | forEach([&out](int v) { out.push_back(v); }) | filter([](int v){ return v % 2 == 0; }) | transform([](int v) { return v * 2; }) | toQList, QList({ 4, 8, 12 })); QCOMPARE(out, in); } } private: template class Container> void testKeysValuesHelper() { const Container in = { { 1, QStringLiteral("1") }, { 2, QStringLiteral("2") }, { 3, QStringLiteral("3") } }; { const QList out = { 1, 2, 3 }; QCOMPARE(out, in | keys | toQList); } { const QStringList out = { QStringLiteral("1"), QStringLiteral("2"), QStringLiteral("3") }; QCOMPARE(out, in | values | toQList); } } private Q_SLOTS: void testKeysValues() { testKeysValuesHelper(); testKeysValuesHelper(); } + + void testAll() + { + const QList vals = { 2, 4, 6, 8, 10 }; + QVERIFY(vals | all([](int v) { return v % 2 == 0; })); + QVERIFY(!(vals | all([](int v) { return v % 2 == 1; }))); + } + + void testAny() + { + const QList vals = { 1, 3, 5, 7, 9 }; + QVERIFY(vals | any([](int v) { return v % 2 == 1; })); + QVERIFY(!(vals | any([](int v) { return v % 2 == 0; }))); + } }; QList AkRangesTest::ForEachCallable::sOut; QTEST_GUILESS_MAIN(AkRangesTest) #include "akrangestest.moc" diff --git a/src/shared/akranges.h b/src/shared/akranges.h index 6f9c04f1b..c95c0b560 100644 --- a/src/shared/akranges.h +++ b/src/shared/akranges.h @@ -1,481 +1,527 @@ /* Copyright (C) 2018 - 2019 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_AKRANGES_H #define AKONADI_AKRANGES_H #include "aktraits.h" #include "akhelpers.h" #include #include #include #include #include #include #include #include namespace Akonadi { namespace detail { template class Cont> struct To_ { template using Container = Cont; }; struct Values_ {}; struct Keys_ {}; template && AkTraits::isReservable) > OutContainer copyContainer(const InContainer &in) { OutContainer rv; rv.reserve(in.size()); for (auto &&v : in) { rv.push_back(std::move(v)); } return rv; } template) > OutContainer copyContainer(const InContainer &in) { OutContainer rv; for (const auto &v : in) { rv.insert(v); } return rv; } template struct IteratorTrait { using iterator_category = typename Iterator::iterator_category; using value_type = typename Iterator::value_type; using difference_type = typename Iterator::difference_type; using pointer = typename Iterator::pointer; using reference = typename Iterator::reference; }; // Without QT_STRICT_ITERATORS QVector and QList iterators do not satisfy STL // iterator concepts since they are nothing more but typedefs to T* - for those // we need to provide custom traits. template struct IteratorTrait { // QTypedArrayData::iterator::iterator_category using iterator_category = std::random_access_iterator_tag; using value_type = Iterator; using difference_type = int; using pointer = Iterator*; using reference = Iterator&; }; template struct IteratorTrait { using iterator_category = std::random_access_iterator_tag; using value_type = Iterator; using difference_type = int; using pointer = const Iterator *; using reference = const Iterator &; }; template struct IteratorBase { public: using iterator_category = typename IteratorTrait::iterator_category; using value_type = typename IteratorTrait::value_type; using difference_type = typename IteratorTrait::difference_type; using pointer = typename IteratorTrait::pointer; using reference = typename IteratorTrait::reference; IteratorBase(const IteratorBase &other) : mIter(other.mIter), mContainer(other.mContainer) {} IterImpl &operator++() { ++static_cast(this)->mIter; return *static_cast(this); } IterImpl operator++(int) { auto ret = *static_cast(this); ++static_cast(this)->mIter; return ret; } bool operator==(const IterImpl &other) const { return mIter == other.mIter; } bool operator!=(const IterImpl &other) const { return !(*static_cast(this) == other); } bool operator<(const IterImpl &other) const { return mIter < other.mIter; } auto operator-(const IterImpl &other) const { return mIter - other.mIter; } auto operator*() const { return *mIter; } protected: IteratorBase(const Iterator &iter, const Container &container) : mIter(iter), mContainer(container) {} IteratorBase(const Iterator &iter, Container &&container) : mIter(iter), mContainer(std::move(container)) {} Iterator mIter; Container mContainer; }; template struct TransformIterator : public IteratorBase, Container> { private: template struct ResultOf; template struct ResultOf { using type = R; }; template using FuncHelper = decltype(Akonadi::invoke(std::declval() ...))(Ts ...); using IteratorValueType = typename ResultOf::value_type>>::type; public: using value_type = IteratorValueType; using pointer = IteratorValueType *; // FIXME: preserve const-ness using reference = const IteratorValueType &; // FIXME: preserve const-ness TransformIterator(const Iterator &iter, const TransformFn &fn, const Container &container) : IteratorBase, Container>(iter, container) , mFn(fn) { } auto operator*() const { return Akonadi::invoke(mFn, *this->mIter); } private: TransformFn mFn; }; template class FilterIterator : public IteratorBase, Container> { public: FilterIterator(const Iterator &iter, const Iterator &end, const Predicate &predicate, const Container &container) : IteratorBase, Container>(iter, container) , mPredicate(predicate), mEnd(end) { while (this->mIter != mEnd && !Akonadi::invoke(mPredicate, *this->mIter)) { ++this->mIter; } } auto &operator++() { if (this->mIter != mEnd) { do { ++this->mIter; } while (this->mIter != mEnd && !Akonadi::invoke(mPredicate, *this->mIter)); } return *this; } auto operator++(int) { auto it = *this; ++(*this); return it; } private: Predicate mPredicate; Iterator mEnd; }; template class AssociativeContainerIterator : public IteratorBase, Container, Iterator> { public: using value_type = std::remove_const_t::type>>; using pointer = std::add_pointer_t; using reference = std::add_lvalue_reference_t; AssociativeContainerIterator(const Iterator &iter, const Container &container) : IteratorBase, Container, Iterator>(iter, container) {} auto operator*() const { return std::get(*this->mIter); } }; template using AssociativeContainerKeyIterator = AssociativeContainerIterator; template using AssociativeContainerValueIterator = AssociativeContainerIterator; template struct Range { public: using iterator = Iterator; using const_iterator = Iterator; using value_type = typename detail::IteratorTrait::value_type; Range(Iterator &&begin, Iterator &&end) : mBegin(std::move(begin)) , mEnd(std::move(end)) {} Iterator begin() const { return mBegin; } Iterator cbegin() const { return mBegin; } Iterator end() const { return mEnd; } Iterator cend() const { return mEnd; } auto size() const { return std::distance(mBegin, mEnd); } private: Iterator mBegin; Iterator mEnd; }; template using IsRange = typename std::is_same>; template struct Transform_ { TransformFn mFn; }; template struct Filter_ { PredicateFn mFn; }; template struct ForEach_ { EachFun mFn; }; +template +struct All_ +{ + Predicate mFn; +}; + +template +struct Any_ +{ + Predicate mFn; +}; + } // namespace detail } // namespace Akonadi // Generic operator| for To_<> convertor template class OutContainer, typename T = typename InContainer::value_type > auto operator|(const InContainer &in, const Akonadi::detail::To_ &) -> OutContainer { using namespace Akonadi::detail; return copyContainer>(in); } // Specialization for case when InContainer and OutContainer are identical // Create a copy, but for Qt container this is very cheap due to implicit sharing. template class InContainer, typename T > auto operator|(const InContainer &in, const Akonadi::detail::To_ &) -> InContainer { return in; } // Generic operator| for transform() template auto operator|(const InContainer &in, const Akonadi::detail::Transform_ &t) { using namespace Akonadi::detail; using OutIt = TransformIterator; return Range(OutIt(std::cbegin(in), t.mFn, in), OutIt(std::cend(in), t.mFn, in)); } // Generic operator| for filter() template auto operator|(const InContainer &in, const Akonadi::detail::Filter_ &p) { using namespace Akonadi::detail; using OutIt = FilterIterator; return Range(OutIt(std::cbegin(in), std::cend(in), p.mFn, in), OutIt(std::cend(in), std::cend(in), p.mFn, in)); } // Generic operator| fo foreach() template auto operator|(const InContainer &in, Akonadi::detail::ForEach_ fun) { std::for_each(std::cbegin(in), std::cend(in), [&fun](const auto &val) { Akonadi::invoke(fun.mFn, val); }); return in; } +// Generic operator| for all +template +auto operator|(const InContainer &in, + Akonadi::detail::All_ fun) +{ + return std::all_of(std::cbegin(in), std::cend(in), fun.mFn); +} + +// Generic operator| for any +template +auto operator|(const InContainer &in, + Akonadi::detail::Any_ fun) +{ + return std::any_of(std::cbegin(in), std::cend(in), fun.mFn); +} // Generic operator| for keys template auto operator|(const InContainer &in, Akonadi::detail::Keys_) { using namespace Akonadi::detail; using OutIt = AssociativeContainerKeyIterator; return Range(OutIt(in.constKeyValueBegin(), in), OutIt(in.constKeyValueEnd(), in)); } // Generic operator| for values template auto operator|(const InContainer &in, Akonadi::detail::Values_) { using namespace Akonadi::detail; using OutIt = AssociativeContainerValueIterator; return Range(OutIt(in.constKeyValueBegin(), in), OutIt(in.constKeyValueEnd(), in)); } + namespace Akonadi { /// Non-lazily convert given range or container to QVector static constexpr auto toQVector = detail::To_{}; /// Non-lazily convert given range or container to QSet static constexpr auto toQSet = detail::To_{}; /// Non-lazily convert given range or container to QList static constexpr auto toQList = detail::To_{}; /// Lazily extract values from an associative container static constexpr auto values = detail::Values_{}; /// Lazily extract keys from an associative container static constexpr auto keys = detail::Keys_{}; /// Lazily transform each element of a range or container using given transformation template detail::Transform_ transform(TransformFn &&fn) { return detail::Transform_{std::forward(fn)}; } /// Lazily filters a range or container by applying given predicate on each element template detail::Filter_ filter(PredicateFn &&fn) { return detail::Filter_{std::forward(fn)}; } /// Non-lazily call EachFun for each element of the container or range template detail::ForEach_ forEach(EachFun &&fn) { return detail::ForEach_{std::forward(fn)}; } /// Create a range, a view on a container from the given pair fo iterators template > detail::Range range(Iterator1 begin, Iterator2 end) { return detail::Range(std::move(begin), std::move(end)); } +/// Non-lazily check that all elements in the range satisfy given predicate +template +detail::All_ all(Predicate &&fn) +{ + return detail::All_{std::forward(fn)}; +} + +/// Non-lazily check that at least one element in range satisfies the given predicate +template +detail::Any_ any(Predicate &&fn) +{ + return detail::Any_{std::forward(fn)}; +} + } // namespace Akonadi #endif