diff --git a/autotests/shared/akrangestest.cpp b/autotests/shared/akrangestest.cpp index f798f6273..adbf6640c 100644 --- a/autotests/shared/akrangestest.cpp +++ b/autotests/shared/akrangestest.cpp @@ -1,167 +1,194 @@ /* 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; class AkRangesTest : public QObject { Q_OBJECT private Q_SLOTS: 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); } 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); + } + } + + void testFilterTransform() + { + { + QStringList in = { QStringLiteral("foo"), QStringLiteral("foobar"), QStringLiteral("foob") }; + QList out = { 6 }; + QCOMPARE(in | transform([](const auto &str) { return str.size(); }) + | filter([](int i) { return i > 5; }) + | toQList, + out); + QCOMPARE(in | filter([](const auto &str) { return str.size() > 5; }) + | transform([](const auto &str) { return str.size(); }) + | toQList, + out); + } + } }; QTEST_GUILESS_MAIN(AkRangesTest) #include "akrangestest.moc" diff --git a/src/shared/akranges.h b/src/shared/akranges.h index d9de42643..d6923df06 100644 --- a/src/shared/akranges.h +++ b/src/shared/akranges.h @@ -1,309 +1,380 @@ /* 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. */ #ifndef AKONADI_AKRANGES_H #define AKONADI_AKRANGES_H #ifndef QT_STRICT_ITERATORS // Without strict iterator QVector::iterator is just a typedef to T*, // which breaks some of the template magic below. QT_STRICT_ITERATORS // are a good thing anyway... #error AkRanges requires QT_STRICT_ITERATORS to be enabled. #endif #include #include #include + #include #include #include #include namespace Akonadi { namespace detail { template class Cont> struct To_ { template using Container = Cont; }; template OutContainer copyContainer(const InContainer &in, std::true_type) { OutContainer rv; rv.reserve(in.size()); - std::move(std::begin(in), std::end(in), std::back_inserter(rv)); + for (auto &&v : in) { + rv.push_back(std::move(v)); + } return rv; } template OutContainer copyContainer(const InContainer &in, std::false_type) { OutContainer rv; for (const auto &v : in) { rv.insert(v); // can't use std::inserter on QSet, sadly :/ } return rv; } template using void_type = void; template class, typename = void_type<>> struct has_method: std::false_type {}; template class Op> struct has_method>>: std::true_type {}; template using push_back = decltype(std::declval().push_back({})); template struct transformType { using type = decltype(std::declval()(Arg{})); }; template struct transformType { using type = Arg; }; template struct transformIteratorType { using type = typename transformType::type; }; template using transformIteratorType_t = typename transformIteratorType::type; template struct IteratorBase { public: 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; IteratorBase(const IteratorBase &other) : mIter(other.mIter) {} IterImpl &operator++() { - mIter++; + ++static_cast(this)->mIter; return *static_cast(this); } IterImpl operator++(int) { - auto ret = static_cast(this); - ++(*static_cast(this)); + 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 !(*this == other); + return !(*static_cast(this) == other); } bool operator<(const IterImpl &other) const { return mIter < other.mIter; } - auto operator*() const + auto operator-(const IterImpl &other) const { - return *mIter; + return mIter - other.mIter; } - auto operator-(const IterImpl &other) const + auto operator*() const { - return mIter - other.mIter; + return *mIter; } protected: IteratorBase(const Iterator &iter) : mIter(iter) {} Iterator mIter; }; template > struct TransformIterator : public IteratorBase, Iterator> { 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) : IteratorBase, Iterator>(iter) , mFn(fn) {} auto operator*() const { return mFn(*(this->mIter)); } private: TransformFn mFn; }; +template +class FilterIterator : public IteratorBase, Iterator> +{ +public: + FilterIterator(const Iterator &iter, const Iterator &end, const Predicate &predicate) + : IteratorBase, Iterator>(iter) + , mPredicate(predicate), mEnd(end) + { + while (this->mIter != mEnd && !mPredicate(*this->mIter)) { + ++this->mIter; + } + } + + FilterIterator &operator++() + { + if (this->mIter != mEnd) { + do { + ++this->mIter; + } while (this->mIter != mEnd && !mPredicate(*this->mIter)); + } + return *this; + } + + FilterIterator operator++(int) + { + auto it = *this; + ++(*this); + return it; + } + +private: + Predicate mPredicate; + Iterator mEnd; +}; + template struct Range { public: using iterator = Iterator; using const_iterator = Iterator; using value_type = typename Iterator::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 mEnd - mBegin; } private: Iterator mBegin; Iterator mEnd; }; template using IsRange = typename std::is_same>; template struct Transform_ { - using Fn = TransformFn; + Transform_(const TransformFn &fn) + : mFn(fn) + {} + + const TransformFn &mFn; +}; + +template +struct Filter_ +{ + Filter_(const PredicateFn &fn) + : mFn(fn) + {} - Transform_(TransformFn &&fn): mFn(std::forward(fn)) {} - TransformFn &&mFn; + const PredicateFn &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, has_method, push_back>{}); } // Specialization for case when InContainer and OutContainer are identical 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; + using OutIt = TransformIterator; return Range(OutIt(in.cbegin(), t.mFn), OutIt(in.cend(), t.mFn)); } + +// 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(in.cbegin(), in.cend(), p.mFn), + OutIt(in.cend(), in.cend(), p.mFn)); +} + + namespace Akonadi { static constexpr auto toQVector = detail::To_{}; static constexpr auto toQSet = detail::To_{}; static constexpr auto toQList = detail::To_{}; template detail::Transform_ transform(TransformFn &&fn) { return detail::Transform_(std::forward(fn)); } +template +detail::Filter_ filter(PredicateFn &&fn) +{ + return detail::Filter_(std::forward(fn)); +} + template > detail::Range range(Iterator1 begin, Iterator2 end) { return detail::Range(std::move(begin), std::move(end)); } } // namespace Akonadi #endif