Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit fc98526f authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Improve AkRanges, add transform operation

parent f6d621d5
......@@ -270,6 +270,7 @@ include_directories(
)
#add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000)
add_definitions(-DQT_STRICT_ITERATORS)
add_subdirectory(src)
add_subdirectory(icons)
......
......@@ -2,3 +2,4 @@ add_subdirectory(private)
add_subdirectory(server)
add_subdirectory(libs)
add_subdirectory(akonadicontrol)
add_subdirectory(shared)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${Akonadi_SOURCE_DIR}/src/shared)
macro(add_unit_test _source)
set(_test ${_source})
get_filename_component(_name ${_source} NAME_WE)
add_executable(${_name} ${_source})
add_test(NAME AkonadiShared-${_name} COMMAND ${_name})
if (ENABLE_ASAN)
set_tests_properties(AkonadiShared-${_name} PROPERTIES
ENVIRONMENT ASAN_OPTIONS=symbolize=1
)
endif()
target_link_libraries(${_name}
akonadi_shared
Qt5::Test
${CMAKE_EXE_LINKER_FLAGS_ASAN}
)
endmacro()
add_unit_test(akrangestest.cpp)
/*
Copyright (c) 2018 Daniel Vrátil <dvratil@kde.org>
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 <QObject>
#include <QTest>
#include <shared/akranges.h>
using namespace Akonadi;
class AkRangesTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testContainerConversion()
{
{
QVector<int> 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<int> 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<int> in = { 1, 2, 3, 4, 5 };
Akonadi::detail::Range<QList<int>::const_iterator> range(in.cbegin(), in.cend());
QCOMPARE(in | Akonadi::toQVector, QVector<int>::fromList(in));
}
{
QVector<int> in = { 1, 2, 3, 4, 5 };
Akonadi::detail::Range<QVector<int>::const_iterator> range(in.cbegin(), in.cend());
QCOMPARE(in | toQList, in.toList());
}
}
void testTransform()
{
QList<int> in = { 1, 2, 3, 4, 5 };
QList<int> 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<CopyCounter> in = { {} }; // 1st copy (QList::append())
QList<CopyCounter> 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<CopyCounter> in(1); // construct vector of one element, so no copying
// occurs at initialization
QVector<CopyCounter> 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<int> in = { 1, 2, 3, 4, 5 };
QVector<int> out = { 2, 4, 6, 8, 10 };
QCOMPARE(in | transform([](int i) { return i * 2; }) | toQVector, out);
}
{
QVector<int> in = { 1, 2, 3, 4, 5 };
QList<int> out = { 2, 4, 6, 8, 10 };
QCOMPARE(in | transform([](int i) { return i * 2; }) | toQList, out);
}
}
};
QTEST_GUILESS_MAIN(AkRangesTest)
#include "akrangestest.moc"
......@@ -20,9 +20,16 @@
#ifndef AKONADI_AKRANGES_H
#define AKONADI_AKRANGES_H
#ifndef QT_STRICT_ITERATORS
// Without strict iterator QVector<T>::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 <QVector>
#include <QSet>
#include <QDebug>
#include <algorithm>
#include <functional>
......@@ -30,19 +37,10 @@ namespace Akonadi {
namespace detail {
struct ToQVector
{
template<typename T> using Container = QVector<T>;
};
struct ToQSet
template<template<typename> class Cont>
struct To_
{
template<typename T> using Container = QSet<T>;
};
struct ToQList
{
template<typename T> using Container = QList<T>;
template<typename T> using Container = Cont<T>;
};
template<typename InContainer, typename OutContainer>
......@@ -50,7 +48,7 @@ OutContainer copyContainer(const InContainer &in, std::true_type)
{
OutContainer rv;
rv.reserve(in.size());
std::copy(std::begin(in), std::end(in), std::back_inserter(rv));
std::move(std::begin(in), std::end(in), std::back_inserter(rv));
return rv;
}
......@@ -78,33 +76,167 @@ struct has_method<T, Op, void_type<Op<T>>>: std::true_type
template <typename T>
using push_back = decltype(std::declval<T>().push_back({}));
template<typename Iterator, typename TransformFn = void*>
struct LazyIterator : public std::iterator<
typename Iterator::iterator_category,
typename Iterator::value_type,
typename Iterator::difference_type,
typename Iterator::pointer,
typename Iterator::reference>
{
public:
LazyIterator(const Iterator &iter): mIter(iter) {};
LazyIterator(const Iterator &iter, const TransformFn &fn)
: mIter(iter), mFn(fn) {}
LazyIterator<Iterator, TransformFn> &operator++()
{
mIter++;
return *this;
}
LazyIterator<Iterator, TransformFn> operator++(int)
{
auto ret = *this;
++(*this);
return ret;
};
bool operator==(const LazyIterator<Iterator, TransformFn> &other) const
{
return mIter == other.mIter;
}
bool operator!=(const LazyIterator<Iterator, TransformFn> &other) const
{
return mIter != other.mIter;
}
auto operator*() const
{
return std::move(getValue<TransformFn>(mIter));
}
auto operator-(const LazyIterator<Iterator, TransformFn> &other) const
{
return mIter - other.mIter;
}
const Iterator &iter() const
{
return mIter;
}
private:
/*
template<typename T>
typename std::enable_if<std::is_same<T, void*>::value, typename Iterator::value_type>::type
getValue(const Iterator &iter) const
{
return *iter;
}
*/
template<typename T>
typename std::enable_if<!std::is_same<T, void*>::value, typename Iterator::value_type>::type
getValue(const Iterator &iter, std::true_type = {}) const
{
return mFn(*iter);
}
Iterator mIter;
TransformFn mFn = {};
};
template<typename Iterator, typename TransformFn = void*>
struct Range
{
public:
using iterator = Iterator;
using value_type = typename Iterator::value_type;
Range(Iterator &&begin, Iterator &&end)
: mBegin(begin), mEnd(end) {}
Range(Iterator &&begin, Iterator &&end, const TransformFn &fn)
: mBegin(begin, fn), mEnd(end, fn) {}
LazyIterator<Iterator, TransformFn> begin() const
{
return mBegin;
}
LazyIterator<Iterator, TransformFn> end() const
{
return mEnd;
}
auto size() const
{
return mEnd.iter() - mBegin.iter();
}
private:
LazyIterator<Iterator, TransformFn> mBegin;
LazyIterator<Iterator, TransformFn> mEnd;
};
template<typename T>
using IsRange = typename std::is_same<T, Range<typename T::iterator>>;
template<typename TransformFn>
struct Transform_
{
using Fn = TransformFn;
Transform_(TransformFn &&fn): mFn(std::forward<TransformFn>(fn)) {}
TransformFn &&mFn;
};
} // namespace detail
} // namespace Akonadi
template<typename InRange, template<typename> class OutContainer,
typename T = typename InRange::value_type>
typename std::enable_if<Akonadi::detail::IsRange<InRange>::value, OutContainer<T>>::type
operator|(const InRange &in, const Akonadi::detail::To_<OutContainer> &)
{
using namespace Akonadi::detail;
return copyContainer<InRange, OutContainer<T>>(
in, has_method<OutContainer<T>, push_back>{});
}
// Magic to pipe container with a toQFoo object as a conversion
template<typename InContainer, typename OutFn,
typename OutContainer = typename OutFn::template Container<typename InContainer::value_type>>
auto operator|(const InContainer &in, const OutFn &) -> OutContainer
template<typename InContainer, template<typename> class OutContainer,
typename T = typename InContainer::value_type>
typename std::enable_if<!Akonadi::detail::IsRange<InContainer>::value, OutContainer<T>>::type
operator|(const InContainer &in, const Akonadi::detail::To_<OutContainer> &)
{
static_assert(std::is_same<typename InContainer::value_type,
typename OutContainer::value_type>::value,
"We can only convert container types, not the value types.");
static_assert(!std::is_same<InContainer, OutContainer>::value,
static_assert(!std::is_same<InContainer, OutContainer<T>>::value,
"Wait, are you trying to convert a container to the same type?");
return Akonadi::detail::copyContainer<InContainer, OutContainer>(
in, Akonadi::detail::has_method<OutContainer, Akonadi::detail::push_back>{});
using namespace Akonadi::detail;
return copyContainer<InContainer, OutContainer<T>>(
in, has_method<OutContainer<T>, push_back>{});
}
template<typename InContainer, typename TransformFn,
typename It = typename InContainer::const_iterator>
auto operator|(const InContainer &in, const Akonadi::detail::Transform_<TransformFn> &t)
{
using namespace Akonadi::detail;
return Range<It, TransformFn>(in.cbegin(), in.cend(), *reinterpret_cast<const TransformFn*>(&t));
}
namespace Akonadi {
static constexpr auto toQVector = detail::ToQVector{};
static constexpr auto toQSet = detail::ToQSet{};
static constexpr auto toQList = detail::ToQList{};
static constexpr auto toQVector = detail::To_<QVector>{};
static constexpr auto toQSet = detail::To_<QSet>{};
static constexpr auto toQList = detail::To_<QList>{};
} // namespace Akonadi
template<typename TransformFn>
detail::Transform_<TransformFn> transform(TransformFn &&fn)
{
return detail::Transform_<TransformFn>(std::forward<TransformFn>(fn));
}
} // namespace Akonadi
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment