/// The MIT License (MIT) /// Copyright (c) 2016 Peter Goldsborough /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to /// deal in the Software without restriction, including without limitation the /// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or /// sell copies of the Software, and to permit persons to whom the Software is /// furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS /// IN THE SOFTWARE. #ifndef BASE_ORDERED_ITERATOR_HPP #define BASE_ORDERED_ITERATOR_HPP #include #include #include #include #include #include #include #include #include #include #include namespace LRU { namespace Internal { template using BaseForBaseOrderedIterator = BaseIterator::const_iterator>; /// The base class for all const and non-const ordered iterators. /// /// Ordered iterators are bidirectional iterators that iterate over the keys of /// a cache in the order in which they were inserted into the cache. As they /// only iterate over the keys, they must perform hash lookups to retrieve the /// value the first time they are dereferenced. This makes them slightly less /// efficient than unordered iterators. However, they also have the additional /// property that they may be constructed from unordered iterators, and that /// they can be decremented. /// /// \tparam Key The key type over which instances of the iterator iterate. /// \tparam Value The value type over which instances of the iterator iterate. /// \tparam Cache The type of the cache instances of the iterator point into. template class BaseOrderedIterator : public BaseForBaseOrderedIterator { protected: using super = BaseForBaseOrderedIterator; using PRIVATE_BASE_ITERATOR_MEMBERS; using UnderlyingIterator = typename Queue::const_iterator; public: using Tag = LRU::Tag::OrderedIterator; using PUBLIC_BASE_ITERATOR_MEMBERS; /// Constructor. BaseOrderedIterator() noexcept = default; /// \copydoc BaseIterator::BaseIterator(Cache,UnderlyingIterator) BaseOrderedIterator(Cache& cache, UnderlyingIterator iterator) : super(cache, iterator) { } /// Generalized copy constructor. /// /// \param other The ordered iterator to contruct from. template BaseOrderedIterator( const BaseOrderedIterator& other) : super(other) { } /// Generalized move constructor. /// /// \param other The ordered iterator to move into this one. template BaseOrderedIterator(BaseOrderedIterator&& other) : super(std::move(other)) { } /// Generalized conversion copy constructor. /// /// \param unordered_iterator The unordered iterator to construct from. template < typename AnyCache, typename UnderlyingIterator, typename = std::enable_if_t< std::is_same, std::decay_t>::value>> BaseOrderedIterator(const BaseUnorderedIterator& unordered_iterator) { // Atomicity _throw_if_at_invalid(unordered_iterator); _cache = unordered_iterator._cache; _iterator = unordered_iterator._iterator->second.order; } /// Generalized conversion move constructor. /// /// \param unordered_iterator The unordered iterator to move-construct from. template < typename AnyCache, typename UnderlyingIterator, typename = std::enable_if_t< std::is_same, std::decay_t>::value>> BaseOrderedIterator(BaseUnorderedIterator&& unordered_iterator) { // Atomicity _throw_if_at_invalid(unordered_iterator); _cache = std::move(unordered_iterator._cache); _entry = std::move(unordered_iterator._entry); _iterator = std::move(unordered_iterator._iterator->second.order); } /// Copy constructor. BaseOrderedIterator(const BaseOrderedIterator& other) = default; /// Move constructor. BaseOrderedIterator(BaseOrderedIterator&& other) = default; /// Copy assignment operator. BaseOrderedIterator& operator=(const BaseOrderedIterator& other) = default; /// Move assignment operator. BaseOrderedIterator& operator=(BaseOrderedIterator&& other) = default; /// Destructor. virtual ~BaseOrderedIterator() = default; /// Checks for equality between this iterator and another ordered iterator. /// /// \param other The other ordered iterator. /// \returns True if both iterators point to the same entry, else false. bool operator==(const BaseOrderedIterator& other) const noexcept { return this->_iterator == other._iterator; } /// Checks for inequality between this iterator another ordered iterator. /// /// \param other The other ordered iterator. /// \returns True if the iterators point to different entries, else false. bool operator!=(const BaseOrderedIterator& other) const noexcept { return !(*this == other); } /// Checks for inequality between this iterator and another unordered /// iterator. /// /// \param other The other unordered iterator. /// \returns True if both iterators point to the end of the same cache, else /// the result of comparing with the unordered iterator, converted to an /// ordered iterator. template bool operator==( const BaseUnorderedIterator& other) const noexcept { if (this->_cache != other._cache) return false; // The past-the-end iterators of the same cache should compare equal. // This is an exceptional guarantee we make. This is also the reason // why we can't rely on the conversion from unordered to ordered iterators // because construction of an ordered iterator from the past-the-end // unordered iterator will fail (with an InvalidIteratorConversion error) if (other == other._cache->unordered_end()) { return *this == this->_cache->ordered_end(); } // Will call the other overload return *this == static_cast(other); } /// Checks for equality between an unordered iterator and an ordered iterator. /// /// \param first The unordered iterator. /// \param second The ordered iterator. /// \returns True if both iterators point to the end of the same cache, else /// the result of comparing with the unordered iterator, converted to an /// ordered iterator. template friend bool operator==( const BaseUnorderedIterator& first, const BaseOrderedIterator& second) noexcept { return second == first; } /// Checks for inequality between an unordered /// iterator and an ordered iterator. /// /// \param first The ordered iterator. /// \param second The unordered iterator. /// \returns True if the iterators point to different entries, else false. template friend bool operator!=(const BaseOrderedIterator& first, const BaseUnorderedIterator& second) noexcept { return !(first == second); } /// Checks for inequality between an unordered /// iterator and an ordered iterator. /// /// \param first The unordered iterator. /// \param second The ordered iterator. /// \returns True if the iterators point to different entries, else false. template friend bool operator!=( const BaseUnorderedIterator& first, const BaseOrderedIterator& second) noexcept { return second != first; } /// Increments the iterator to the next entry. /// /// If the iterator already pointed to the end any number of increments /// before, behavior is undefined. /// /// \returns The resulting iterator. BaseOrderedIterator& operator++() { ++_iterator; _entry.reset(); return *this; } /// Increments the iterator and returns a copy of the previous one. /// /// If the iterator already pointed to the end any number of increments /// before, behavior is undefined. /// /// \returns A copy of the previous iterator. BaseOrderedIterator operator++(int) { auto previous = *this; ++*this; return previous; } /// Decrements the iterator to the previous entry. /// /// \returns The resulting iterator. BaseOrderedIterator& operator--() { --_iterator; _entry.reset(); return *this; } /// Decrements the iterator and returns a copy of the previous entry. /// /// \returns The previous iterator. BaseOrderedIterator operator--(int) { auto previous = *this; --*this; return previous; } Entry& operator*() noexcept override { return _maybe_lookup(); } /// \returns A reference to the entry the iterator points to. /// \details If the iterator is invalid, behavior is undefined. Entry& entry() override { _cache->throw_if_invalid(*this); return _maybe_lookup(); } /// \returns A reference to the value the iterator points to. /// \details If the iterator is invalid, behavior is undefined. Value& value() override { return entry().value(); } /// \returns A reference to the key the iterator points to. /// \details If the iterator is invalid, behavior is undefined. const Key& key() override { // No lookup required _cache->throw_if_invalid(*this); return *_iterator; } protected: template friend class BaseOrderedIterator; /// Looks up the entry for a key if this was not done already. /// /// \returns The entry, which was possibly newly looked up. Entry& _maybe_lookup() { if (!_entry.has_value()) { _lookup(); } return *_entry; } /// Looks up the entry for a key and sets the internal entry member. void _lookup() { auto iterator = _cache->_map.find(*_iterator); _entry.emplace(iterator->first, iterator->second.value); } private: /// Throws an exception if the given unordered iterator is invalid. /// /// \param unordered_iterator The iterator to check. /// \throws LRU::Error::InvalidIteratorConversion if the iterator is invalid. template void _throw_if_at_invalid(const UnorderedIterator& unordered_iterator) { // For atomicity of the copy assignment, we assign the cache pointer only // after this check in the copy/move constructor and use the iterator's // cache. If an exception is thrown, the state of the ordered iterator is // unchanged compared to before the assignment. if (unordered_iterator == unordered_iterator._cache->unordered_end()) { throw LRU::Error::InvalidIteratorConversion(); } } }; } // namespace Internal } // namespace LRU #endif // BASE_ORDERED_ITERATOR_HPP