diff --git a/autotests/shared/akrangestest.cpp b/autotests/shared/akrangestest.cpp index 31e2a3ac9..f798f6273 100644 --- a/autotests/shared/akrangestest.cpp +++ b/autotests/shared/akrangestest.cpp @@ -1,157 +1,167 @@ /* 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 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); + } + } + }; QTEST_GUILESS_MAIN(AkRangesTest) #include "akrangestest.moc" diff --git a/src/shared/akranges.h b/src/shared/akranges.h index 0ab44b578..066c2297c 100644 --- a/src/shared/akranges.h +++ b/src/shared/akranges.h @@ -1,266 +1,286 @@ /* 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)); 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 LazyIterator : public std::iterator< - typename Iterator::iterator_category, - typename Iterator::value_type, - typename Iterator::difference_type, - typename Iterator::pointer, - typename Iterator::reference> +template +struct transformType +{ + using type = decltype(std::declval()(Arg{})); +}; + +template +struct transformType +{ + using type = Arg; +}; + +template +struct transformIteratorType +{ + using type = typename transformType::type; +}; + +template::type> +struct LazyIterator { public: + using iterator_category = typename Iterator::iterator_category; + using value_type = IteratorValueType; + using difference_type = typename Iterator::difference_type; + using pointer = IteratorValueType *; // FIXME: preserve const-ness + using reference = const IteratorValueType &; // FIXME: preserve const-ness + LazyIterator(const Iterator &iter): mIter(iter) {}; LazyIterator(const Iterator &iter, const TransformFn &fn) : mIter(iter), mFn(fn) {} LazyIterator &operator++() { mIter++; return *this; } LazyIterator operator++(int) { auto ret = *this; ++(*this); return ret; }; bool operator==(const LazyIterator &other) const { return mIter == other.mIter; } bool operator!=(const LazyIterator &other) const { return mIter != other.mIter; } bool operator<(const LazyIterator &other) const { return mIter < other.mIter; } auto operator*() const { return std::move(getValue(mIter)); } auto operator-(const LazyIterator &other) const { return mIter - other.mIter; } const Iterator &iter() const { return mIter; } private: template typename std::enable_if::value, typename Iterator::value_type>::type getValue(const Iterator &iter) const { return *iter; } template - typename std::enable_if::value, typename Iterator::value_type>::type + typename std::enable_if::value, value_type>::type getValue(const Iterator &iter, std::true_type = {}) const { return mFn(*iter); } Iterator mIter; TransformFn mFn = {}; }; template struct Range { public: using iterator = Iterator; using lazy_iterator = LazyIterator; - using value_type = typename Iterator::value_type; + using value_type = typename transformIteratorType::type; Range(Iterator &&begin, Iterator &&end) : mBegin(std::move(begin)), mEnd(std::move(end)) {} Range(Iterator &&begin, Iterator &&end, const TransformFn &fn) : mBegin(std::move(begin), fn), mEnd(std::move(end), fn) {} LazyIterator begin() const { return mBegin; } LazyIterator end() const { return mEnd; } auto size() const { return mEnd.iter() - mBegin.iter(); } private: LazyIterator mBegin; LazyIterator mEnd; }; template using IsRange = typename std::is_same>; template struct Transform_ { using Fn = TransformFn; Transform_(TransformFn &&fn): mFn(std::forward(fn)) {} TransformFn &&mFn; }; } // namespace detail } // namespace Akonadi template class OutContainer, typename T = typename InRange::value_type> typename std::enable_if::value, OutContainer>::type operator|(const InRange &in, const Akonadi::detail::To_ &) { using namespace Akonadi::detail; return copyContainer>( in, has_method, push_back>{}); } // Magic to pipe container with a toQFoo object as a conversion template class OutContainer, typename T = typename InContainer::value_type> typename std::enable_if::value, OutContainer>::type operator|(const InContainer &in, const Akonadi::detail::To_ &) { static_assert(!std::is_same>::value, "Wait, are you trying to convert a container to the same type?"); using namespace Akonadi::detail; return copyContainer>( in, has_method, push_back>{}); } template typename std::enable_if::value, Akonadi::detail::Range>::type operator|(const InContainer &in, const Akonadi::detail::Transform_ &t) { using namespace Akonadi::detail; return Range(in.cbegin(), in.cend(), *reinterpret_cast(&t)); } template typename std::enable_if::value, Akonadi::detail::Range>::type operator|(const InRange &in, const Akonadi::detail::Transform_ &t) { using namespace Akonadi::detail; return Range(in.begin(), in.end(), *reinterpret_cast(&t)); } 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::type> detail::Range range(Iterator1 begin, Iterator2 end) { return detail::Range(std::move(begin), std::move(end)); } } // namespace Akonadi #endif