diff --git a/CMakeLists.txt b/CMakeLists.txt index b5658e414..12a06689f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,334 +1,335 @@ cmake_minimum_required(VERSION 3.5) set(PIM_VERSION "5.10.46") project(Akonadi VERSION ${PIM_VERSION}) if (MSVC) # On Windows our target compiler is MSVC 14.14, which supports C++17, so # we can enable it. MSVC does not ship experimental/optional and similar # and disallows use of C++17 classes in C++14 mode. set(CMAKE_CXX_STANDARD 17) else() # On Linux, MacOS and BSD we need to support older compilers that only # offer C++14. set(CMAKE_CXX_STANDARD 14) set(WITH_3RDPARTY_OPTIONAL TRUE) endif() # ECM setup set(KF5_VERSION "5.53.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(CheckIncludeFiles) include(ECMQtDeclareLoggingCategory) include(CheckSymbolExists) include(KDEPackageAppTemplates) include(ECMMarkNonGuiExecutable) include(AkonadiMacros) set(QT_REQUIRED_VERSION "5.9.0") set(AKONADI_VERSION ${PIM_VERSION}) ecm_setup_version(PROJECT VARIABLE_PREFIX AKONADI VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/akonadi_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfigVersion.cmake" SOVERSION 5) # Find packages find_package(Qt5Core ${QT_REQUIRED_VERSION} REQUIRED COMPONENTS Private) find_package(Qt5Sql ${QT_REQUIRED_VERSION} REQUIRED COMPONENTS Private) find_package(Qt5DBus ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Network ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Test ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Widgets ${QT_REQUIRED_VERSION} REQUIRED) find_package(Qt5Xml ${QT_REQUIRED_VERSION} REQUIRED) find_package(KF5Completion ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5DBusAddons ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5ItemViews ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Crash ${KF5_VERSION} CONFIG REQUIRED) find_package(Qt5Designer NO_MODULE) set_package_properties(Qt5Designer PROPERTIES PURPOSE "Required to build the Qt Designer plugins" TYPE OPTIONAL ) find_package(KF5DesignerPlugin ${KF5_VERSION} CONFIG) set_package_properties(KF5DesignerPlugin PROPERTIES DESCRIPTION "KF5 designer plugin" TYPE OPTIONAL) set(Boost_MINIMUM_VERSION "1.34.0") find_package(Boost ${Boost_MINIMUM_VERSION}) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "http://www.boost.org" TYPE REQUIRED ) if(BUILD_TESTING) set(AKONADI_TESTS_EXPORT AKONADICORE_EXPORT) set(AKONADIWIDGET_TESTS_EXPORT AKONADIWIDGETS_EXPORT) add_definitions(-DBUILD_TESTING) endif() configure_file(akonaditests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonaditests_export.h") configure_file(akonadiwidgetstests_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/akonadiwidgetstests_export.h") # Make sure the KF5Akonadi_DATA_DIR is absolute before passing it to KF5AkonadiConfig.cmake.in # otherwise build fails either on OSX CI, or for normal users if (IS_ABSOLUTE "${KDE_INSTALL_DATADIR_KF5}") set(KF5Akonadi_DATA_DIR "${KDE_INSTALL_DATADIR_KF5}/akonadi") else() set(KF5Akonadi_DATA_DIR "${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_DATADIR_KF5}/akonadi") endif() check_symbol_exists(malloc_trim "malloc.h" HAVE_MALLOC_TRIM) ############### Build Options ############### option(AKONADI_BUILD_QSQLITE "Build the Sqlite backend." TRUE) option(BUILD_TOOLS "Build and install tools for development and testing purposes." TRUE) if(BUILD_TESTING) set(BUILD_TOOLS TRUE) endif() set(SMI_MIN_VERSION "1.3") find_package(SharedMimeInfo ${SMI_MIN_VERSION} REQUIRED) find_program(XSLTPROC_EXECUTABLE xsltproc) if(NOT XSLTPROC_EXECUTABLE) message(FATAL_ERROR "\nThe command line XSLT processor program 'xsltproc' could not be found.\nPlease install xsltproc.\n") endif() find_program(MYSQLD_EXECUTABLE NAMES mysqld PATHS /usr/sbin /usr/local/sbin /usr/libexec /usr/local/libexec /opt/mysql/libexec /usr/mysql/bin /opt/mysql/sbin DOC "The mysqld executable path. ONLY needed at runtime" ) if(MYSQLD_EXECUTABLE) message(STATUS "MySQL Server found: ${MYSQLD_EXECUTABLE}") else() message(STATUS "MySQL Server wasn't found. it is required to use the MySQL backend.") endif() find_path(POSTGRES_PATH NAMES pg_ctl HINTS /usr/lib${LIB_SUFFIX}/postgresql/8.4/bin /usr/lib${LIB_SUFFIX}/postgresql/9.0/bin /usr/lib${LIB_SUFFIX}/postgresql/9.1/bin DOC "The pg_ctl executable path. ONLY needed at runtime by the PostgreSQL backend" ) if(POSTGRES_PATH) message(STATUS "PostgreSQL Server found.") else() message(STATUS "PostgreSQL wasn't found. it is required to use the Postgres backend.") endif() if("${DATABASE_BACKEND}" STREQUAL "SQLITE") set(SQLITE_TYPE "REQUIRED") else() set(SQLITE_TYPE "OPTIONAL") endif() if(AKONADI_BUILD_QSQLITE) set(SQLITE_MIN_VERSION 3.6.23) find_package(Sqlite ${SQLITE_MIN_VERSION}) set_package_properties(Sqlite PROPERTIES URL "http://www.sqlite.org" DESCRIPTION "The Sqlite database library" TYPE ${SQLITE_TYPE} PURPOSE "Required by the Sqlite backend" ) endif() find_program(XMLLINT_EXECUTABLE xmllint) if(NOT XMLLINT_EXECUTABLE) message(STATUS "xmllint not found, skipping akonadidb.xml schema validation") endif() check_include_files(unistd.h HAVE_UNISTD_H) if(HAVE_UNISTD_H) add_definitions(-DHAVE_UNISTD_H) endif() if (IS_ABSOLUTE "${DBUS_INTERFACES_INSTALL_DIR}") set(AKONADI_DBUS_INTERFACES_INSTALL_DIR "${DBUS_INTERFACES_INSTALL_DIR}") else() set(AKONADI_DBUS_INTERFACES_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${DBUS_INTERFACES_INSTALL_DIR}") endif() if (IS_ABSOLUTE "${KDE_INSTALL_INCLUDEDIR_KF5}") set(AKONADI_INCLUDE_DIR "${KDE_INSTALL_INCLUDEDIR_KF5}") else() set(AKONADI_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_INCLUDEDIR_KF5}") endif() ############### Build Options ############### include(CTest) # Calls enable_testing(). include(CTestConfig.cmake) if(NOT DEFINED DATABASE_BACKEND) set(DATABASE_BACKEND "MYSQL" CACHE STRING "The default database backend to use for Akonadi. Can be either MYSQL, POSTGRES or SQLITE") endif() ############### CTest options ############### # Set a timeout value of 1 minute per test set(DART_TESTING_TIMEOUT 60) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake COPYONLY) ############### Macros ############### macro(SET_DEFAULT_DB_BACKEND) set(_backend ${ARGV0}) if("${_backend}" STREQUAL "SQLITE") set(AKONADI_DATABASE_BACKEND QSQLITE3) set(AKONADI_BUILD_QSQLITE TRUE) else() if("${_backend}" STREQUAL "POSTGRES") set(AKONADI_DATABASE_BACKEND QPSQL) else() set(AKONADI_DATABASE_BACKEND QMYSQL) endif() endif() message(STATUS "Using default db backend ${AKONADI_DATABASE_BACKEND}") add_definitions(-DAKONADI_DATABASE_BACKEND="${AKONADI_DATABASE_BACKEND}") endmacro() #### DB BACKEND DEFAULT #### set_default_db_backend(${DATABASE_BACKEND}) ############### Compilers flags ############### if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER MATCHES "icc" OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-long-long -std=iso9899:1990 -Wundef -Wcast-align -Werror-implicit-function-declaration -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wwrite-strings -Wformat-security -Wmissing-format-attribute -fno-common") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wundef -Wcast-align -Wchar-subscripts -Wall -Wextra -Wpointer-arith -Wformat-security -fno-common -pedantic") set(CMAKE_CXX_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C++ compiler during debugfull builds." FORCE) set(CMAKE_C_FLAGS_DEBUGFULL "-g3 -fno-inline" CACHE STRING "Flags used by the C compiler during debugfull builds." FORCE) mark_as_advanced(CMAKE_CXX_FLAGS_DEBUGFULL CMAKE_C_FLAGS_DEBUGFULL) elseif (MSVC) # This sets the __cplusplus macro to a real value based on the version of C++ specified by # the /std switch. Without it MSVC keeps reporting C++98, so feature detection doesn't work. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus") endif() if(MSVC) set(_ENABLE_EXCEPTIONS -EHsc) else() set(_ENABLE_EXCEPTIONS -fexceptions) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_ENABLE_EXCEPTIONS}") ############### Configure files ############# configure_file(akonadi-prefix.h.cmake ${Akonadi_BINARY_DIR}/akonadi-prefix.h) configure_file(config-akonadi.h.cmake ${Akonadi_BINARY_DIR}/config-akonadi.h) ############### build targets ############### add_definitions(-DTRANSLATION_DOMAIN=\"libakonadi5\") include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} src ) #add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) +add_definitions(-DQT_STRICT_ITERATORS) add_subdirectory(src) add_subdirectory(icons) add_subdirectory(templates) if(BUILD_TOOLS) # add testrunner (application for managing a self-contained # test environment) add_subdirectory(autotests/libs/testrunner) add_subdirectory(autotests/libs/testresource) add_subdirectory(autotests/libs/testsearchplugin) endif() if(BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() ############### install stuff ############### install(FILES akonadi-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) ############### CMake Config Files ############### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Akonadi") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5AkonadiConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS AKONADI_DBUS_INTERFACES_INSTALL_DIR AKONADI_INCLUDE_DIR KF5Akonadi_DATA_DIR ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5AkonadiConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5AkonadiMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5AkonadiTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5AkonadiTargets.cmake NAMESPACE KF5::) install(FILES akonadi.categories akonadi.renamecategories DESTINATION ${KDE_INSTALL_CONFDIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadi_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index a88fd9757..f7ec5d78e 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(private) add_subdirectory(server) add_subdirectory(libs) add_subdirectory(akonadicontrol) +add_subdirectory(shared) diff --git a/autotests/shared/CMakeLists.txt b/autotests/shared/CMakeLists.txt new file mode 100644 index 000000000..1414af22f --- /dev/null +++ b/autotests/shared/CMakeLists.txt @@ -0,0 +1,22 @@ +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) diff --git a/autotests/shared/akrangestest.cpp b/autotests/shared/akrangestest.cpp new file mode 100644 index 000000000..be5810df8 --- /dev/null +++ b/autotests/shared/akrangestest.cpp @@ -0,0 +1,140 @@ +/* + 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); + } + } + +}; + +QTEST_GUILESS_MAIN(AkRangesTest) + +#include "akrangestest.moc" diff --git a/src/shared/akranges.h b/src/shared/akranges.h index fd91f453f..fccaad418 100644 --- a/src/shared/akranges.h +++ b/src/shared/akranges.h @@ -1,110 +1,242 @@ /* 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 namespace Akonadi { namespace detail { -struct ToQVector -{ - template using Container = QVector; -}; - -struct ToQSet +template class Cont> +struct To_ { - template using Container = QSet; -}; - -struct ToQList -{ - template using Container = QList; + template using Container = Cont; }; template 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; } 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> +{ +public: + 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; + } + + 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 + getValue(const Iterator &iter, std::true_type = {}) const + { + return mFn(*iter); + } + + Iterator mIter; + TransformFn mFn = {}; +}; + +template +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 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> -auto operator|(const InContainer &in, const OutFn &) -> OutContainer +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, - "We can only convert container types, not the value types."); - static_assert(!std::is_same::value, + static_assert(!std::is_same>::value, "Wait, are you trying to convert a container to the same type?"); - return Akonadi::detail::copyContainer( - in, Akonadi::detail::has_method{}); + using namespace Akonadi::detail; + return copyContainer>( + in, has_method, push_back>{}); } +template +auto operator|(const InContainer &in, const Akonadi::detail::Transform_ &t) +{ + using namespace Akonadi::detail; + return Range(in.cbegin(), in.cend(), *reinterpret_cast(&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_{}; +static constexpr auto toQSet = detail::To_{}; +static constexpr auto toQList = detail::To_{}; -} // namespace Akonadi +template +detail::Transform_ transform(TransformFn &&fn) +{ + return detail::Transform_(std::forward(fn)); +} +} // namespace Akonadi #endif