diff --git a/autotests/shared/akrangestest.cpp b/autotests/shared/akrangestest.cpp index 6761b4281..335042b5f 100644 --- a/autotests/shared/akrangestest.cpp +++ b/autotests/shared/akrangestest.cpp @@ -1,254 +1,340 @@ /* 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 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); + 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([](const auto &str) { return str.size(); }) + QCOMPARE(in | transform(&QString::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(); }) + | 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); } } }; +QList AkRangesTest::ForEachCallable::sOut; + QTEST_GUILESS_MAIN(AkRangesTest) #include "akrangestest.moc" diff --git a/src/shared/akhelpers.h b/src/shared/akhelpers.h new file mode 100644 index 000000000..619edc6da --- /dev/null +++ b/src/shared/akhelpers.h @@ -0,0 +1,117 @@ +/* + 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_AKHELPERS_H_ +#define AKONADI_AKHELPERS_H_ + +#include +#include + +namespace Akonadi +{ +namespace detail +{ + +// C++14-compatible implementation of std::invoke(), based on implementation +// from https://en.cppreference.com/w/cpp/utility/functional/invoke + +template +struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; +template +constexpr bool is_reference_wrapper_v = is_reference_wrapper::value; + +template +constexpr bool is_member_function_pointer_v = std::is_member_function_pointer>>::value; + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && std::is_base_of>::value, + std::result_of_t> +{ + return (std::forward(t1).*fun)(std::forward(args) ...); +} + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && is_reference_wrapper_v>, + std::result_of_t> +{ + return (t1.get().*fun)(std::forward(args) ...); +} + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && !std::is_base_of>::value + && !is_reference_wrapper_v>, + std::result_of_t> +{ + return ((*std::forward(t1)).*fun)(std::forward(args) ...); +} + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && std::is_base_of>::value, + std::result_of_t> +{ + return std::forward(t1).*fun; +} + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && is_reference_wrapper_v>, + std::result_of_t> +{ + return t1.get().*fun; +} + +template +auto invoke(Type T::* fun, T1 &&t1, Args && ... args) -> + std::enable_if_t + && !std::is_base_of>::value + && !is_reference_wrapper_v>, + std::result_of_t> +{ + return (*std::forward(t1)).*fun; +} + +template +auto invoke(Fun &&fun, Args && ... args) +{ + return std::forward(fun)(std::forward(args) ...); +} + +} // namespace detail + +template +auto invoke(Fun &&fun, Args && ... args) +{ + return detail::invoke(std::forward(fun), std::forward(args) ...); +} + + +} // namespace Akonadi + +#endif diff --git a/src/shared/akranges.h b/src/shared/akranges.h index abe130358..7a1bce622 100644 --- a/src/shared/akranges.h +++ b/src/shared/akranges.h @@ -1,407 +1,402 @@ /* 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 #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 "aktraits.h" +#include "akhelpers.h" #include #include #include #include #include #include #include namespace Akonadi { namespace detail { template class Cont> struct To_ { template using Container = Cont; }; 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 -using void_t = void; - -template -struct transformType; - -template -struct transformType()(std::declval()))>> -{ - using type = decltype(std::declval()(std::declval())); -}; - -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), 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 + typename Iterator = typename Container::const_iterator > 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>::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 mFn(*(this->mIter)); + 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 && !mPredicate(*this->mIter)) { + while (this->mIter != mEnd && !Akonadi::invoke(mPredicate, *this->mIter)) { ++this->mIter; } } auto &operator++() { if (this->mIter != mEnd) { do { ++this->mIter; - } while (this->mIter != mEnd && !mPredicate(*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 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_ { - const TransformFn &mFn; + TransformFn mFn; }; template struct Filter_ { - const PredicateFn &mFn; + PredicateFn mFn; }; template struct ForEach_ { - const EachFun &mFn; + EachFun 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, - const Akonadi::detail::ForEach_ &fun) + Akonadi::detail::ForEach_ fun) { - std::for_each(std::cbegin(in), std::cend(in), fun.mFn); + std::for_each(std::cbegin(in), std::cend(in), + [&fun](const auto &val) { + Akonadi::invoke(fun.mFn, val); + }); return 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 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)); } } // namespace Akonadi #endif