From a57e640398da3e35b3087756a5b77059a51a7800 Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Tue, 10 Dec 2013 16:09:37 +0000 Subject: [PATCH] generic type-erased container types CSequence and CCollection, including predicate-based algorithms refs #81 --- docs/Doxyfile.cmake.in | 2 +- docs/Doxyfile.qmake | 2 +- src/blackmisc/blackmisc.h | 15 + src/blackmisc/blackmiscfreefunctions.h | 1 + src/blackmisc/collection.h | 291 +++++++++++ src/blackmisc/containerbase.h | 324 ++++++++++++ src/blackmisc/iterator.h | 653 +++++++++++++++++++++++++ src/blackmisc/pqphysicalquantity.h | 2 +- src/blackmisc/predicates.h | 146 ++++++ src/blackmisc/sequence.h | 355 ++++++++++++++ src/blackmisc/valueobject.cpp | 5 + src/blackmisc/valueobject.h | 13 + tests/blackmisc/testblackmiscmain.cpp | 3 + tests/blackmisc/testcontainers.cpp | 68 +++ tests/blackmisc/testcontainers.h | 28 ++ 15 files changed, 1905 insertions(+), 3 deletions(-) create mode 100644 src/blackmisc/collection.h create mode 100644 src/blackmisc/containerbase.h create mode 100644 src/blackmisc/iterator.h create mode 100644 src/blackmisc/predicates.h create mode 100644 src/blackmisc/sequence.h create mode 100644 tests/blackmisc/testcontainers.cpp create mode 100644 tests/blackmisc/testcontainers.h diff --git a/docs/Doxyfile.cmake.in b/docs/Doxyfile.cmake.in index 76a3fa2bd..f1ccb0ee1 100644 --- a/docs/Doxyfile.cmake.in +++ b/docs/Doxyfile.cmake.in @@ -1621,7 +1621,7 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED := Q_COMPILER_VARIADIC_TEMPLATES # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. diff --git a/docs/Doxyfile.qmake b/docs/Doxyfile.qmake index 4ae658543..c5849e9a5 100644 --- a/docs/Doxyfile.qmake +++ b/docs/Doxyfile.qmake @@ -1621,7 +1621,7 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED := Q_COMPILER_VARIADIC_TEMPLATES # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. diff --git a/src/blackmisc/blackmisc.h b/src/blackmisc/blackmisc.h index 823a75453..58a32cab4 100644 --- a/src/blackmisc/blackmisc.h +++ b/src/blackmisc/blackmisc.h @@ -34,4 +34,19 @@ * \brief Classes for physical quantities and units such as length, mass, speed. */ +/*! + * \namespace BlackMisc::Predicates + * \brief Functor classes for evaluating predicate calculus expressions. + */ + +/*! + * \internal + * \namespace BlackMisc::Predicates::Private + */ + +/*! + * \namespace BlackMisc::Iterators + * \brief Iterator classes for the containers. + */ + #endif diff --git a/src/blackmisc/blackmiscfreefunctions.h b/src/blackmisc/blackmiscfreefunctions.h index 89881d17c..0f36b589a 100644 --- a/src/blackmisc/blackmiscfreefunctions.h +++ b/src/blackmisc/blackmiscfreefunctions.h @@ -6,6 +6,7 @@ #ifndef BLACKMISC_FREEFUNCTIONS_H #define BLACKMISC_FREEFUNCTIONS_H +#include "valueobject.h" // for qHash overload #include // for Q_INIT_RESOURCE #include #include diff --git a/src/blackmisc/collection.h b/src/blackmisc/collection.h new file mode 100644 index 000000000..1fe81225b --- /dev/null +++ b/src/blackmisc/collection.h @@ -0,0 +1,291 @@ +/* Copyright (C) 2013 VATSIM Community / authors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*! + \file +*/ + +#ifndef BLACKMISC_COLLECTION_H +#define BLACKMISC_COLLECTION_H + +#include "iterator.h" +#include "containerbase.h" +#include +#include +#include +#include +#include + +namespace BlackMisc +{ + + /*! + * \brief Generic type-erased unsequenced container with value semantics. + * \tparam T the type of elements contained. + * + * Can take any suitable container class as its implementation at runtime. + */ + template + class CCollection : public CContainerBase + { + public: + //! \brief STL compatibility + //! @{ + typedef T key_type; + typedef T value_type; + typedef T &reference; + typedef const T &const_reference; + typedef T *pointer; + typedef const T *const_pointer; + typedef typename Iterators::ConstForwardIterator const_iterator; + typedef const_iterator iterator; // can't modify elements in-place + typedef ptrdiff_t difference_type; + typedef int size_type; + //! @} + + /*! + * \brief Default constructor. + */ + CCollection() : m_pimpl(new Pimpl>(QSet())) {} + + /*! + * \brief Constructor. + * \tparam C Becomes the collection's implementation type. + * \param c Initial value for the collection; typically empty, but could contain elements. + */ + template CCollection(C c) : m_pimpl(new Pimpl(std::move(c))) {} + + /*! + * \brief Copy constructor. + * \param other + */ + CCollection(const CCollection &other) : m_pimpl(other.pimpl() ? other.pimpl()->clone() : nullptr) {} + + /*! + * \brief Move constructor. + * \param other + */ + CCollection(CCollection &&other) : m_pimpl(other.m_pimpl.take()) {} + + /*! + * \brief Assignment. + * \tparam C Becomes the collection's new implementation type. + * \param c New value for the collection; typically empty, but could contain elements. + */ + template CCollection &operator =(C c) { m_pimpl.reset(new Pimpl(std::move(c))); return *this; } + + /*! + * \brief Copy assignment. + * \param other + * \return + */ + CCollection &operator =(const CCollection &other) { m_pimpl.reset(other.pimpl() ? other.pimpl()->clone() : nullptr); return *this; } + + /*! + * \brief Move assignment. + * \param other + * \return + */ + CCollection &operator =(CCollection && other) { m_pimpl.reset(other.m_pimpl.take()); return *this; } + + /*! + * \brief Change the implementation type but keep all the same elements, by copying them into the new implementation. + * \tparam C Becomes the collection's new implementation type. + */ + template void changeImpl(C = C()) { CCollection c = C(); for (auto i = cbegin(); i != cend(); ++i) c.insert(*i); *this = std::move(c); } + + /*! + * \brief Like changeImpl, but uses the implementation type of another collection. + * \param other + * \pre The other collection must be initialized. + */ + void useImplOf(const CCollection &other) { PimplPtr p = other.pimpl()->cloneEmpty(); for (auto i = cbegin(); i != cend(); ++i) p->insert(*i); m_pimpl.reset(p.take()); } + + /*! + * \brief Returns iterator at the beginning of the collection. + * \return + */ + iterator begin() { return pimpl() ? pimpl()->begin() : iterator(); } + + /*! + * \brief Returns iterator at the beginning of the collection. + * \return + */ + const_iterator begin() const { return pimpl() ? pimpl()->begin() : const_iterator(); } + + /*! + * \brief Returns iterator at the beginning of the collection. + * \return + */ + const_iterator cbegin() const { return pimpl() ? pimpl()->cbegin() : const_iterator(); } + + /*! + * \brief Returns iterator one past the end of the collection. + * \return + */ + iterator end() { return pimpl() ? pimpl()->end() : iterator(); } + + /*! + * \brief Returns iterator one past the end of the collection. + * \return + */ + const_iterator end() const { return pimpl() ? pimpl()->end() : const_iterator(); } + + /*! + * \brief Returns iterator one past the end of the collection. + * \return + */ + const_iterator cend() const { return pimpl() ? pimpl()->cend() : const_iterator(); } + + /*! + * \brief Swap this collection with another. + * \param other + */ + void swap(CCollection &other) { m_pimpl.swap(other.m_pimpl); } + + /*! + * \brief Returns number of elements in the collection. + * \return + */ + size_type size() const { return pimpl() ? pimpl()->size() : 0; } + + /*! + * \brief Returns true if the collection is empty. + * \return + */ + bool empty() const { return pimpl() ? pimpl()->empty() : true; } + + /*! + * \brief Synonym for empty. + * \return + */ + bool isEmpty() const { return empty(); } + + /*! + * \brief Removes all elements in the collection. + */ + void clear() { if (pimpl()) pimpl()->clear(); } + + /*! + * \brief Inserts an element into the collection. + * \param value + * \return An iterator to the position where value was inserted. + * \pre The collection must be initialized. + */ + iterator insert(const T &value) { Q_ASSERT(pimpl()); return pimpl()->insert(value); } + + /*! + * \brief Synonym for insert. + * \param value + * \return An iterator to the position where value was inserted. + * \pre The collection must be initialized. + */ + iterator push_back(const T &value) { return insert(value); } + + /*! + * \brief Remove the element pointed to by the given iterator. + * \param pos + * \return An iterator to the position of the next element after the one removed. + * \pre The collection must be initialized. + */ + iterator erase(iterator pos) { Q_ASSERT(pimpl()); return pimpl()->erase(pos); } + + /*! + * \brief Remove the range of elements between two iterators. + * \param it1 + * \param it2 + * \return An iterator to the position of the next element after the one removed. + * \pre The sequence must be initialized. + */ + iterator erase(iterator it1, iterator it2) { Q_ASSERT(pimpl()); return pimpl()->erase(it1, it2); } + + /*! + * \brief Test for equality. + * \param other + * \return + * \todo Improve inefficient implementation. + */ + bool operator ==(const CCollection &other) const { return (empty() && other.empty()) ? true : (size() != other.size() ? false : *pimpl() == *other.pimpl()); } + + /*! + * \brief Test for inequality. + * \param other + * \return + * \todo Improve inefficient implementation. + */ + bool operator !=(const CCollection &other) const { return !(*this == other); } + + private: + class PimplBase + { + public: + virtual ~PimplBase() {} + virtual PimplBase *clone() const = 0; + virtual PimplBase *cloneEmpty() const = 0; + virtual iterator begin() = 0; + virtual const_iterator begin() const = 0; + virtual const_iterator cbegin() const = 0; + virtual iterator end() = 0; + virtual const_iterator end() const = 0; + virtual const_iterator cend() const = 0; + virtual size_type size() const = 0; + virtual bool empty() const = 0; + virtual void clear() = 0; + virtual iterator insert(const T &value) = 0; + virtual iterator erase(iterator pos) = 0; + virtual iterator erase(iterator it1, iterator it2) = 0; + virtual bool operator ==(const PimplBase &other) const = 0; + protected: + // using SFINAE to choose whether to implement insert() in terms of either push_back() or insert(), depending on which is available + // https://groups.google.com/forum/#!original/comp.lang.c++.moderated/T3x6lvmvvkQ/mfY5VTDJ--UJ + class yes { char x; }; class no { yes x[2]; }; template struct typecheck {}; + struct base { void push_back(); }; template struct derived : public C, public base {}; + static yes hasPushHelper(...); template static no hasPushHelper(D *, typecheck * = 0); + template struct hasPush : public std::integral_constant*)0)) == sizeof(yes)> {}; + template static iterator insertImpl(typename std::enable_if< hasPush::value, C>::type &c, const T &value) { c.push_back(value); return c.end() - 1; } + template static iterator insertImpl(typename std::enable_if < !hasPush::value, C >::type &c, const T &value) { return c.insert(value); } + }; + + template class Pimpl : public PimplBase + { + public: + static_assert(std::is_same::value, "CCollection must be initialized from a container with the same value_type."); + Pimpl(C &&c) : m_impl(std::move(c)) {} + PimplBase *clone() const { return new Pimpl(*this); } + PimplBase *cloneEmpty() const { return new Pimpl(C()); } + iterator begin() { return m_impl.begin(); } + const_iterator begin() const { return m_impl.cbegin(); } + const_iterator cbegin() const { return m_impl.cbegin(); } + iterator end() { return m_impl.end(); } + const_iterator end() const { return m_impl.cend(); } + const_iterator cend() const { return m_impl.cend(); } + size_type size() const { return m_impl.size(); } + bool empty() const { return m_impl.empty(); } + void clear() { m_impl.clear(); } + iterator insert(const T &value) { return PimplBase::insertImpl(m_impl, value); } + iterator erase(iterator pos) { return m_impl.erase(*static_cast(pos.getImpl())); } + iterator erase(iterator it1, iterator it2) { return m_impl.erase(*static_cast(it1.getImpl(), it2.getImpl())); } + bool operator ==(const PimplBase &other) const { Pimpl copy = C(); for (auto i = other.cbegin(); i != other.cend(); ++i) copy.insert(*i); return m_impl == copy.m_impl; } + private: + C m_impl; + }; + + typedef QScopedPointer PimplPtr; + PimplPtr m_pimpl; + + // using these methods to access m_pimpl.data() eases the cognitive burden of correctly forwarding const + PimplBase *pimpl() { return m_pimpl.data(); } + const PimplBase *pimpl() const { return m_pimpl.data(); } + }; + +} //namespace BlackMisc + +Q_DECLARE_METATYPE(BlackMisc::CCollection) +Q_DECLARE_METATYPE(BlackMisc::CCollection) +Q_DECLARE_METATYPE(BlackMisc::CCollection) +Q_DECLARE_METATYPE(BlackMisc::CCollection) +// CCollection not instantiated because QSet is not supported due to hashing constraints + +#endif // guard diff --git a/src/blackmisc/containerbase.h b/src/blackmisc/containerbase.h new file mode 100644 index 000000000..c7b44e879 --- /dev/null +++ b/src/blackmisc/containerbase.h @@ -0,0 +1,324 @@ +/* Copyright (C) 2013 VATSIM Community / authors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/*! + \file +*/ + +#ifndef BLACKMISC_CONTAINERBASE_H +#define BLACKMISC_CONTAINERBASE_H + +#include "valueobject.h" +#include "valuemap.h" +#include "predicates.h" +#include + +#define _SCL_SECURE_NO_WARNINGS // suppress MSVC unchecked iterator warning for std::transform + +namespace BlackMisc +{ + + /*! + * \brief Base class for CCollection and CSequence implementing their algorithms. + */ + template