Dependencies: Add lru-cache library

Adds a generic C++ based caching library to support material external
modules. This is a template based library which means there is nothing
to build or link.

The only modification is to the CMakeLists.txt file to enable
integration with FreeCAD,

Original source: https://github.com/goldsborough/lru-cache
This commit is contained in:
David Carter
2025-04-02 00:07:25 -04:00
committed by Chris Hennes
parent 393ab112e5
commit 09f71cb40d
49 changed files with 10596 additions and 0 deletions

View File

@@ -9,6 +9,10 @@ if(NOT FREECAD_USE_EXTERNAL_E57FORMAT)
add_subdirectory(libE57Format)
endif()
if(BUILD_MATERIAL_EXTERNAL)
add_subdirectory(lru-cache)
endif()
if (BUILD_ASSEMBLY AND NOT FREECAD_USE_EXTERNAL_ONDSELSOLVER)
if( NOT EXISTS "${CMAKE_SOURCE_DIR}/src/3rdParty/OndselSolver/CMakeLists.txt" )
message(FATAL_ERROR "The OndselSolver git submodule is not available. Please run

73
src/3rdParty/lru-cache/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,73 @@
###########################################################
## CMAKE SETUP
###########################################################
cmake_minimum_required(VERSION 3.2)
project(lru-cache)
add_compile_options(-g)
########################################
# C++ VERSIONING
########################################
include(CheckCXXCompilerFlag)
# check_cxx_compiler_flag("-std=c++14" COMPILER_SUPPORTS_CXX_14)
# check_cxx_compiler_flag("-std=c++1z" COMPILER_SUPPORTS_CXX_1z)
# check_cxx_compiler_flag("-std=c++17" COMPILER_SUPPORTS_CXX_17)
# if (COMPILER_SUPPORTS_CXX_1z)
# message(STATUS "Compiling with C++1z")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1z")
# elseif (COMPILER_SUPPORTS_CXX_14)
# message(STATUS "Compiling with C++14")
# set(CMAKE_CXX_STANDARD 14)
# elseif (COMPILER_SUPPORTS_CXX_17)
# message(STATUS "Compiling with C++17")
# set(CMAKE_CXX_STANDARD 17)
# else()
# message(FATAL_ERROR "Please install a modern C++ compiler, they are not expensive.")
# endif()
###########################################################
## DEPENDENCIES
###########################################################
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/cmake/Modules/"
)
###########################################################
## INCLUDES
###########################################################
# Need this top-level include for "tests/"
# include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
###########################################################
## EXAMPLES
###########################################################
# add_subdirectory(examples)
########################################
# TESTS
########################################
# option(BUILD_LRU_CACHE_TESTS "Enable tests" OFF)
# if(BUILD_LRU_CACHE_TESTS)
# message(STATUS "Enabling tests ...")
# enable_testing()
# add_subdirectory(tests)
# else()
# message(STATUS "Disabling tests ...")
# endif()
file (GLOB LRU_HEADERS include/*.hpp)
file (GLOB LRU_INTERNAL_HEADERS include/*.hpp)
install (FILES ${LRU_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include)
install (FILES ${LRU_INTERNAL_HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/internal)

19
src/3rdParty/lru-cache/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
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.

304
src/3rdParty/lru-cache/README.md vendored Normal file

File diff suppressed because one or more lines are too long

3
src/3rdParty/lru-cache/cpplint.cfg vendored Normal file
View File

@@ -0,0 +1,3 @@
# cpplint configuration
filter=-build/c++11,-whitespace/parens,-runtime/references,-whitespace/operators

2310
src/3rdParty/lru-cache/docs/Doxyfile vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
###########################################################
## BINARIES
###########################################################
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/examples)
########################################
# TARGETS
########################################
add_executable(fibonacci-basic fibonacci-basic.cpp)
add_executable(fibonacci-timed fibonacci-timed.cpp)
add_executable(statistics statistics.cpp)
add_executable(callbacks callbacks.cpp)
add_executable(lowercase lowercase.cpp)
add_executable(wrap wrap.cpp)

View File

@@ -0,0 +1,76 @@
/// 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.
#include <iostream>
#include "lru/lru.hpp"
using Cache = LRU::Cache<std::uint64_t, std::uint64_t>;
std::uint64_t fibonacci(std::uint64_t n, Cache& cache) {
if (n < 2) return 1;
// We std::uint64_ternally keep track of the last accessed key, meaning a
// `contains(key)` + `lookup(key)` sequence will involve only a single hash
// table lookup.
if (cache.contains(n)) return cache[n];
auto value = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
// Caches are 100% move-aware and we have implemented
// `unordered_map` style emplacement and insertion.
cache.emplace(n, value);
return value;
}
std::uint64_t fibonacci(std::uint64_t n) {
// Use a capacity of 100 (after 100 insertions, the next insertion will evict
// the least-recently inserted element). The default capacity is 128. Note
// that for fibonacci, a capacity of 2 is sufficient (and ideal).
Cache cache(100);
// clang-format off
cache.hit_callback([](auto& key, auto& value) {
std::clog << "Hit for entry ("
<< key << ", " << value << ")"
<< std::endl;
});
cache.miss_callback([](auto& key) {
std::clog << "Miss for " << key<< std::endl;
});
cache.access_callback([](auto& key, bool was_hit) {
std::clog << "Access for " << key
<< " was a " << (was_hit ? "hit" : "miss")
<< std::endl;
});
// clang-format on
auto value = fibonacci(n, cache);
return value;
}
auto main() -> int {
std::cout << fibonacci(10) << std::endl;
}

View File

@@ -0,0 +1,55 @@
/// 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.
#include <iostream>
#include "lru/lru.hpp"
using Cache = LRU::Cache<int, int>;
int fibonacci(int n, Cache& cache) {
if (n < 2) return 1;
// We internally keep track of the last accessed key, meaning a
// `contains(key)` + `lookup(key)` sequence will involve only a single hash
// table lookup.
if (cache.contains(n)) return cache[n];
auto value = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
// Caches are 100% move-aware and we have implemented
// `unordered_map` style emplacement and insertion.
cache.emplace(n, value);
return value;
}
int fibonacci(int n) {
// Use a capacity of 100 (after 100 insertions, the next insertion will evict
// the least-recently accessed element). The default capacity is 128. Note
// that for fibonacci, a capacity of 2 is sufficient (and ideal).
Cache cache(100);
return fibonacci(n, cache);
}
auto main() -> int {
std::cout << fibonacci(32) << std::endl;
}

View File

@@ -0,0 +1,57 @@
/// 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.
#include <chrono>
#include <iostream>
#include "lru/lru.hpp"
using namespace std::chrono_literals;
using Cache = LRU::TimedCache<int, int>;
int fibonacci(int n, Cache& cache) {
if (n < 2) return 1;
// We internally keep track of the last accessed key, meaning a
// `contains(key)` + `lookup(key)` sequence will involve only a single hash
// table lookup.
if (cache.contains(n)) return cache[n];
auto value = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
// Caches are 100% move-aware and we have implemented
// `unordered_map` style emplacement and insertion.
cache.emplace(n, value);
return value;
}
int fibonacci(int n) {
// Use a time to live of 100ms. This means that 100ms after insertion, a key
// will be said to have "expired" and `contains(key)` will return false.
Cache cache(100ms);
return fibonacci(n, cache);
}
auto main() -> int {
std::cout << fibonacci(32) << std::endl;
}

View File

@@ -0,0 +1,47 @@
/// 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.
#include <chrono>
#include <iostream>
#include "lru/lowercase.hpp"
void print(lru::tag::basic_cache) {
std::cout << "basic cache" << '\n';
}
void print(lru::tag::timed_cache) {
std::cout << "timed cache" << '\n';
}
auto main() -> int {
using namespace std::chrono_literals;
lru::cache<int, int> cache;
lru::timed_cache<int, int> timed_cache(100ms);
print(cache.tag());
print(timed_cache.tag());
lru::cache<int, int>::ordered_const_iterator iterator(cache.begin());
lru::statistics<int> stats;
}

View File

@@ -0,0 +1,76 @@
/// 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.
#include <iostream>
#include "lru/lru.hpp"
using Cache = LRU::Cache<std::uint64_t, std::uint64_t>;
std::uint64_t fibonacci(std::uint64_t n, Cache& cache) {
if (n < 2) return 1;
// We std::uint64_ternally keep track of the last accessed key, meaning a
// `contains(key)` + `lookup(key)` sequence will involve only a single hash
// table lookup.
if (cache.contains(n)) return cache[n];
auto value = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
// Caches are 100% move-aware and we have implemented
// `unordered_map` style emplacement and insertion.
cache.emplace(n, value);
return value;
}
std::uint64_t fibonacci(std::uint64_t n) {
// Use a capacity of 100 (after 100 insertions, the next insertion will evict
// the least-recently inserted element). The default capacity is 128. Note
// that for fibonacci, a capacity of 2 is sufficient (and ideal).
Cache cache(100);
cache.monitor(2, 3, 4, 5);
auto value = fibonacci(n, cache);
for (auto i : {2, 3, 4, 5}) {
auto stats = cache.stats().stats_for(i);
// clang-format off
std::cout << "Statistics for " << i << ": "
<< stats.hits << " hit(s), "
<< stats.misses << " miss(es)."
<< std::endl;
}
// You'll notice we'll always have n - 1 misses, for each time we access
// one of the numbers in [0, n] for the first time.
std::cout << "Overall: "
<< cache.stats().total_hits() << " hit(s), "
<< cache.stats().total_misses() << " miss(es)."
<< std::endl;
// clang-format on
return value;
}
auto main() -> int {
// The last number that fits into a 64 bit unsigned number
std::cout << fibonacci(92) << std::endl;
}

View File

@@ -0,0 +1,45 @@
/// 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.
#include <chrono>
#include <iostream>
#include <thread>
#include "lru/lru.hpp"
using namespace std::chrono_literals;
int f(int x) {
std::this_thread::sleep_for(1s);
return x;
}
auto main() -> int {
// Use a time-to-live of 2 minutes and a
// capacity of 128 for an LRU::TimedCache
auto wrapped = LRU::timed_wrap(f, 2min, 128);
std::cout << "Slow the first time ..." << '\n';
wrapped(42);
std::cout << "Fast the second time!" << '\n';
wrapped(42);
}

View File

@@ -0,0 +1,40 @@
/// 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 LRU_CACHE_TAGS_HPP
#define LRU_CACHE_TAGS_HPP
namespace LRU {
namespace Tag {
struct BasicCache {};
struct TimedCache {};
} // namespace Tag
namespace Lowercase {
namespace tag {
using basic_cache = ::LRU::Tag::BasicCache;
using timed_cache = ::LRU::Tag::TimedCache;
} // namespace tag
} // namespace Lowercase
} // namespace LRU
#endif // LRU_CACHE_TAGS_HPP

View File

@@ -0,0 +1,207 @@
/// 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 LRU_CACHE_HPP
#define LRU_CACHE_HPP
#include <cassert>
#include <chrono>
#include <cstddef>
#include <functional>
#include <iterator>
#include <list>
#include <stdexcept>
#include <unordered_map>
#include <lru/cache-tags.hpp>
#include <lru/error.hpp>
#include <lru/internal/base-cache.hpp>
#include <lru/internal/information.hpp>
#include <lru/internal/last-accessed.hpp>
namespace LRU {
namespace Internal {
template <typename Key,
typename Value,
typename HashFunction,
typename KeyEqual>
using UntimedCacheBase = Internal::BaseCache<Key,
Value,
Internal::Information,
HashFunction,
KeyEqual,
Tag::BasicCache>;
} // namespace Internal
/// A basic LRU cache implementation.
///
/// An LRU cache is a fixed-size cache that remembers the order in which
/// elements were inserted into it. When the size of the cache exceeds its
/// capacity, the "least-recently-used" (LRU) element is erased. In our
/// implementation, usage is defined as insertion, but not lookup. That is,
/// looking up an element does not move it to the "front" of the cache (making
/// the operation faster). Only insertions (and erasures) can change the order
/// of elements. The capacity of the cache can be modified at any time.
///
/// \see LRU::TimedCache
template <typename Key,
typename Value,
typename HashFunction = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>>
class Cache
: public Internal::UntimedCacheBase<Key, Value, HashFunction, KeyEqual> {
private:
using super = Internal::UntimedCacheBase<Key, Value, HashFunction, KeyEqual>;
using PRIVATE_BASE_CACHE_MEMBERS;
public:
using PUBLIC_BASE_CACHE_MEMBERS;
using typename super::size_t;
/// \copydoc BaseCache::BaseCache(size_t,const HashFunction&,const KeyEqual&)
/// \detailss The capacity defaults to an internal constant, currently 128.
explicit Cache(size_t capacity = Internal::DEFAULT_CAPACITY,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, hash, equal) {
}
/// \copydoc BaseCache(size_t,Iterator,Iterator,const HashFunction&,const
/// KeyEqual&)
template <typename Iterator>
Cache(size_t capacity,
Iterator begin,
Iterator end,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, begin, end, hash, equal) {
}
/// \copydoc BaseCache(Iterator,Iterator,const HashFunction&,const
/// KeyEqual&)
template <typename Iterator>
Cache(Iterator begin,
Iterator end,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(begin, end, hash, equal) {
}
/// Constructor.
///
/// \param capacity The capacity of the cache.
/// \param range A range to construct the cache with.
/// \param hash The hash function to use for the internal map.
/// \param key_equal The key equality function to use for the internal map.
template <typename Range, typename = Internal::enable_if_range<Range>>
Cache(size_t capacity,
Range&& range,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, std::forward<Range>(range), hash, equal) {
}
/// Constructor.
///
/// \param range A range to construct the cache with.
/// \param hash The hash function to use for the internal map.
/// \param key_equal The key equality function to use for the internal map.
template <typename Range, typename = Internal::enable_if_range<Range>>
explicit Cache(Range&& range,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(std::forward<Range>(range), hash, equal) {
}
/// \copydoc BaseCache(InitializerList,const HashFunction&,const
/// KeyEqual&)
Cache(InitializerList list,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual()) // NOLINT(runtime/explicit)
: super(list, hash, equal) {
}
/// \copydoc BaseCache(size_t,InitializerList,const HashFunction&,const
/// KeyEqual&)
Cache(size_t capacity,
InitializerList list,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual()) // NOLINT(runtime/explicit)
: super(capacity, list, hash, equal) {
}
/// \copydoc BaseCache::find(const Key&)
UnorderedIterator find(const Key& key) override {
auto iterator = _map.find(key);
if (iterator != _map.end()) {
_register_hit(key, iterator->second.value);
_move_to_front(iterator->second.order);
_last_accessed = iterator;
} else {
_register_miss(key);
}
return {*this, iterator};
}
/// \copydoc BaseCache::find(const Key&) const
UnorderedConstIterator find(const Key& key) const override {
auto iterator = _map.find(key);
if (iterator != _map.end()) {
_register_hit(key, iterator->second.value);
_move_to_front(iterator->second.order);
_last_accessed = iterator;
} else {
_register_miss(key);
}
return {*this, iterator};
}
/// \returns The most-recently inserted element.
const Key& front() const noexcept {
if (is_empty()) {
throw LRU::Error::EmptyCache("front");
} else {
// The queue is reversed for natural order of iteration.
return _order.back();
}
}
/// \returns The least-recently inserted element.
const Key& back() const noexcept {
if (is_empty()) {
throw LRU::Error::EmptyCache("back");
} else {
// The queue is reversed for natural order of iteration.
return _order.front();
}
}
};
namespace Lowercase {
template <typename... Ts>
using cache = Cache<Ts...>;
} // namespace Lowercase
} // namespace LRU
#endif // LRU_CACHE_HPP

View File

@@ -0,0 +1,134 @@
/// 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 LRU_PAIR_HPP
#define LRU_PAIR_HPP
#include <algorithm>
#include <type_traits>
#include <utility>
namespace LRU {
/// A entry of references to the key and value of an entry in a cache.
///
/// Instances of this class are usually the result of dereferencing an iterator.
///
/// \tparam Key The key type of the pair.
/// \tparam Value The value type of the pair.
template <typename Key, typename Value>
struct Entry final {
using KeyType = Key;
using ValueType = Value;
using first_type = Key;
using second_type = Value;
/// Constructor.
///
/// \param key The key of the entry.
/// \param value The value of the entry.
Entry(const Key& key, Value& value) : first(key), second(value) {
}
/// Generalized copy constructor.
///
/// Mainly for conversion from non-const values to const values.
///
/// \param other The entry to construct from.
template <typename AnyKey,
typename AnyValue,
typename =
std::enable_if_t<(std::is_convertible<AnyKey, Key>::value &&
std::is_convertible<AnyValue, Value>::value)>>
Entry(const Entry<AnyKey, AnyValue>& other)
: first(other.first), second(other.second) {
}
/// Compares two entrys for equality.
///
/// \param first The first entry to compare.
/// \param second The second entry to compare.
/// \returns True if the firest entry equals the second, else false.
template <typename Pair, typename = typename Pair::first_type>
friend bool operator==(const Entry& first, const Pair& second) noexcept {
return first.first == second.first && first.second == second.second;
}
/// Compares two entrys for equality.
///
/// \param first The first entry to compare.
/// \param second The second entry to compare.
/// \returns True if the first entry equals the second, else false.
template <typename Pair, typename = typename Pair::first_type>
friend bool operator==(const Pair& first, const Entry& second) noexcept {
return second == first;
}
/// Compares two entrys for inequality.
///
/// \param first The first entry to compare.
/// \param second The second entry to compare.
/// \returns True if the first entry does not equal the second, else false.
template <typename Pair, typename = typename Pair::first_type>
friend bool operator!=(const Entry& first, const Pair& second) noexcept {
return !(first == second);
}
/// Compares two entrys for inequality.
///
/// \param first The first entry to compare.
/// \param second The second entry to compare.fdas
/// \returns True if the first entry does not equal the second, else false.
template <typename Pair, typename = typename Pair::first_type>
friend bool operator!=(const Pair& first, const Entry& second) noexcept {
return second != first;
}
/// \returns A `std::pair` instance with the key and value of this entry.
operator std::pair<const Key&, Value&>() noexcept {
return {first, second};
}
/// \returns The key of the entry (`first`).
const Key& key() const noexcept {
return first;
}
/// \returns The value of the entry (`second`).
Value& value() noexcept {
return second;
}
/// \returns The value of the entry (`second`).
const Value& value() const noexcept {
return second;
}
/// The key of the entry.
const Key& first;
/// The value of the entry.
Value& second;
};
} // namespace LRU
#endif // LRU_PAIR_HPP

View File

@@ -0,0 +1,107 @@
/// 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 LRU_INTERNAL_ERRORS_HPP
#define LRU_INTERNAL_ERRORS_HPP
#include <stdexcept>
#include <string>
namespace LRU {
namespace Error {
/// Exception thrown when the value of an invalid key was requested.
struct KeyNotFound : public std::runtime_error {
using super = std::runtime_error;
KeyNotFound() : super("Failed to find key") {
}
explicit KeyNotFound(const std::string& key)
: super("Failed to find key: " + key) {
}
};
/// Exception thrown when the value of an expired key was requested.
struct KeyExpired : public std::runtime_error {
using super = std::runtime_error;
explicit KeyExpired(const std::string& key)
: super("Key found, but expired: " + key) {
}
KeyExpired() : super("Key found, but expired") {
}
};
/// Exception thrown when requesting the front or end key of an empty cache.
struct EmptyCache : public std::runtime_error {
using super = std::runtime_error;
explicit EmptyCache(const std::string& what_was_expected)
: super("Requested " + what_was_expected + " of empty cache") {
}
};
/// Exception thrown when attempting to convert an invalid unordered iterator to
/// an ordered iterator.
struct InvalidIteratorConversion : public std::runtime_error {
using super = std::runtime_error;
InvalidIteratorConversion()
: super("Cannot convert past-the-end unordered to ordered iterator") {
}
};
/// Exception thrown when attempting to erase the past-the-end iterator.
struct InvalidIterator : public std::runtime_error {
using super = std::runtime_error;
InvalidIterator() : super("Past-the-end iterator is invalid here") {
}
};
/// Exception thrown when requesting statistics about an unmonitored key.
struct UnmonitoredKey : public std::runtime_error {
using super = std::runtime_error;
UnmonitoredKey() : super("Requested statistics for unmonitored key") {
}
};
/// Exception thrown when requesting the statistics object of a cache when none
/// was registered.
struct NotMonitoring : public std::runtime_error {
using super = std::runtime_error;
NotMonitoring() : super("Statistics monitoring not enabled for this cache") {
}
};
namespace Lowercase {
using key_not_found = KeyNotFound;
using key_expired = KeyExpired;
using empty_cache = EmptyCache;
using invalid_iterator_conversion = InvalidIteratorConversion;
using invalid_iterator = InvalidIterator;
using unmonitored_key = UnmonitoredKey;
using not_monitoring = NotMonitoring;
} // namespace Lowercase
} // namespace Error
} // namespace LRU
#endif // LRU_INTERNAL_ERRORS_HPP

View File

@@ -0,0 +1,76 @@
/// 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 LRU_INSERTION_RESULT_HPP
#define LRU_INSERTION_RESULT_HPP
#include <algorithm>
#include <type_traits>
#include <utility>
namespace LRU {
/// The result of an insertion into a cache.
///
/// This is a semantically nicer alternative to a generic `std::pair`, as is
/// returned by `std::unordered_map` or so. It still has the same static
/// interface as the `std::pair` (with `first` and `second` members), but adds
/// nicer `was_inserted()` and `iterator()` accessors.
///
/// \tparam Iterator The class of the iterator contained in the result.
template <typename Iterator>
struct InsertionResult final {
using IteratorType = Iterator;
/// Constructor.
///
/// \param result Whether the result was successful.
/// \param iterator The iterator pointing to the inserted or updated key.
InsertionResult(bool result, Iterator iterator)
: first(result), second(iterator) {
}
/// \returns True if the key was newly inserted, false if it was only updated.
bool was_inserted() const noexcept {
return first;
}
/// \returns The iterator pointing to the inserted or updated key.
Iterator iterator() const noexcept {
return second;
}
/// \copydoc was_inserted
explicit operator bool() const noexcept {
return was_inserted();
}
/// Whether the result was successful.
bool first;
/// The iterator pointing to the inserted or updated key.
Iterator second;
};
} // namespace LRU
#endif // LRU_INSERTION_RESULT_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
/// 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 LRU_INTERNAL_BASE_ITERATOR_HPP
#define LRU_INTERNAL_BASE_ITERATOR_HPP
#include <algorithm>
#include <iterator>
#include <lru/entry.hpp>
#include <lru/internal/optional.hpp>
#define PUBLIC_BASE_ITERATOR_MEMBERS \
typename super::Entry; \
using typename super::KeyType; \
using typename super::ValueType;
#define PRIVATE_BASE_ITERATOR_MEMBERS \
super::_iterator; \
using super::_entry; \
using super::_cache;
namespace LRU {
namespace Internal {
/// The base class for all (ordered and unordered) iterators.
///
/// All iterators over our LRU caches store a reference to the cache they point
/// into, an underlying iterator they adapt (e.g. a map iterator or list
/// iterator) as well as a entry, a reference to which is returned when
/// dereferencing the iterator.
///
/// \tparam IteratorTag A standard iterator category tag.
/// \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.
/// \tparam UnderlyingIterator The underlying iterator class used to implement
/// the iterator.
template <typename IteratorTag,
typename Key,
typename Value,
typename Cache,
typename UnderlyingIterator>
class BaseIterator : public std::iterator<IteratorTag, LRU::Entry<Key, Value>> {
public:
using KeyType = Key;
using ValueType =
std::conditional_t<std::is_const<Cache>::value, const Value, Value>;
using Entry = LRU::Entry<KeyType, ValueType>;
/// Default constructor.
BaseIterator() noexcept : _cache(nullptr) {
}
/// Constructor.
///
/// \param cache The cache this iterator points into.
/// \param iterator The underlying iterator to adapt.
BaseIterator(Cache& cache, const UnderlyingIterator& iterator) noexcept
: _iterator(iterator), _cache(&cache) {
}
/// Copy constructor.
///
/// Differs from the default copy constructor in that it does not copy the
/// entry.
///
/// \param other The base iterator to copy.
BaseIterator(const BaseIterator& other) noexcept
: _iterator(other._iterator), _cache(other._cache) {
// Note: we do not copy the entry, as it would require a new allocation.
// Since iterators are often taken by value, this may incur a high cost.
// As such we delay the retrieval of the entry to the first call to entry().
}
/// Copy assignment operator.
///
/// Differs from the default copy assignment
/// operator in that it does not copy the entry.
///
/// \param other The base iterator to copy.
/// \return The base iterator instance.
BaseIterator& operator=(const BaseIterator& other) noexcept {
if (this != &other) {
_iterator = other._iterator;
_cache = other._cache;
_entry.reset();
}
return *this;
}
/// Move constructor.
BaseIterator(BaseIterator&& other) noexcept = default;
/// Move assignment operator.
BaseIterator& operator=(BaseIterator&& other) noexcept = default;
/// Generalized copy constructor.
///
/// Mainly necessary for non-const to const conversion.
///
/// \param other The base iterator to copy from.
template <typename AnyIteratorTag,
typename AnyKeyType,
typename AnyValueType,
typename AnyCacheType,
typename AnyUnderlyingIteratorType>
BaseIterator(const BaseIterator<AnyIteratorTag,
AnyKeyType,
AnyValueType,
AnyCacheType,
AnyUnderlyingIteratorType>& other)
: _iterator(other._iterator), _entry(other._entry), _cache(other._cache) {
}
/// Generalized move constructor.
///
/// Mainly necessary for non-const to const conversion.
///
/// \param other The base iterator to move into this one.
template <typename AnyIteratorTag,
typename AnyKeyType,
typename AnyValueType,
typename AnyCacheType,
typename AnyUnderlyingIteratorType>
BaseIterator(BaseIterator<AnyIteratorTag,
AnyKeyType,
AnyValueType,
AnyCacheType,
AnyUnderlyingIteratorType>&& other) noexcept
: _iterator(std::move(other._iterator))
, _entry(std::move(other._entry))
, _cache(std::move(other._cache)) {
}
/// Destructor.
virtual ~BaseIterator() = default;
/// Swaps this base iterator with another one.
///
/// \param other The other iterator to swap with.
void swap(BaseIterator& other) noexcept {
// Enable ADL
using std::swap;
swap(_iterator, other._iterator);
swap(_entry, other._entry);
swap(_cache, other._cache);
}
/// Swaps two base iterator.
///
/// \param first The first iterator to swap.
/// \param second The second iterator to swap.
friend void swap(BaseIterator& first, BaseIterator& second) noexcept {
first.swap(second);
}
/// \returns A reference to the current entry pointed to by the iterator.
virtual Entry& operator*() noexcept = 0;
/// \returns A pointer to the current entry pointed to by the iterator.
Entry* operator->() noexcept {
return &(**this);
}
/// \copydoc operator*()
virtual Entry& entry() = 0;
/// \returns A reference to the value of the entry currently pointed to by the
/// iterator.
virtual ValueType& value() = 0;
/// \returns A reference to the key of the entry currently pointed to by the
/// iterator.
virtual const Key& key() = 0;
protected:
template <typename, typename, typename, typename, typename>
friend class BaseIterator;
/// The underlying iterator this iterator class adapts.
UnderlyingIterator _iterator;
/// The entry optionally being stored.
Optional<Entry> _entry;
/// A pointer to the cache this iterator points into.
/// Pointer and not reference because it's cheap to copy.
/// Pointer and not `std::reference_wrapper` because the class needs to be
/// default-constructible.
Cache* _cache;
};
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_BASE_ITERATOR_HPP

View File

@@ -0,0 +1,338 @@
/// 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 <algorithm>
#include <functional>
#include <iterator>
#include <type_traits>
#include <lru/entry.hpp>
#include <lru/error.hpp>
#include <lru/internal/base-iterator.hpp>
#include <lru/internal/base-unordered-iterator.hpp>
#include <lru/internal/definitions.hpp>
#include <lru/internal/optional.hpp>
#include <lru/iterator-tags.hpp>
namespace LRU {
namespace Internal {
template <typename Key, typename Value, typename Cache>
using BaseForBaseOrderedIterator =
BaseIterator<std::bidirectional_iterator_tag,
Key,
Value,
Cache,
typename Queue<Key>::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 <typename Key, typename Value, typename Cache>
class BaseOrderedIterator
: public BaseForBaseOrderedIterator<Key, Value, Cache> {
protected:
using super = BaseForBaseOrderedIterator<Key, Value, Cache>;
using PRIVATE_BASE_ITERATOR_MEMBERS;
using UnderlyingIterator = typename Queue<Key>::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 <typename AnyKey, typename AnyValue, typename AnyCache>
BaseOrderedIterator(
const BaseOrderedIterator<AnyKey, AnyValue, AnyCache>& other)
: super(other) {
}
/// Generalized move constructor.
///
/// \param other The ordered iterator to move into this one.
template <typename AnyKey, typename AnyValue, typename AnyCache>
BaseOrderedIterator(BaseOrderedIterator<AnyKey, AnyValue, AnyCache>&& 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<AnyCache>, std::decay_t<Cache>>::value>>
BaseOrderedIterator(const BaseUnorderedIterator<AnyCache, UnderlyingIterator>&
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<AnyCache>, std::decay_t<Cache>>::value>>
BaseOrderedIterator(BaseUnorderedIterator<AnyCache, UnderlyingIterator>&&
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 <typename AnyCache, typename AnyUnderlyingIterator>
bool operator==(
const BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>& 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<BaseOrderedIterator>(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 <typename AnyCache, typename AnyUnderlyingIterator>
friend bool operator==(
const BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>& 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 <typename AnyCache, typename AnyUnderlyingIterator>
friend bool
operator!=(const BaseOrderedIterator& first,
const BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>&
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 <typename AnyCache, typename AnyUnderlyingIterator>
friend bool operator!=(
const BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>& 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 <typename, typename, typename>
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 <typename UnorderedIterator>
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

View File

@@ -0,0 +1,216 @@
/// 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_UNORDERED_ITERATOR_HPP
#define BASE_UNORDERED_ITERATOR_HPP
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <lru/entry.hpp>
#include <lru/internal/base-iterator.hpp>
#include <lru/internal/definitions.hpp>
#include <lru/internal/optional.hpp>
#include <lru/iterator-tags.hpp>
namespace LRU {
// Forward declaration.
template <typename, typename, typename, typename, typename>
class TimedCache;
namespace Internal {
template <typename Cache, typename UnderlyingIterator>
using BaseForBaseUnorderedIterator =
BaseIterator<std::forward_iterator_tag,
decltype(UnderlyingIterator()->first),
decltype(UnderlyingIterator()->second.value),
Cache,
UnderlyingIterator>;
/// The base class for all const and non-const unordered iterators.
///
/// An unordered iterator is a wrapper around an `unordered_map` iterator with
/// ForwardIterator category. As such, it is (nearly) as fast to access the pair
/// as through the unordered iterator as through the map iterator directly.
/// However, the order of keys is unspecified. For this reason, unordered
/// iterators have the special property that they may be used to construct
/// ordered iterators, after which the order of insertion is respected.
///
/// \tparam Cache The type of the cache instances of the iterator point into.
/// \tparam UnderlyingIterator The underlying iterator class used to implement
/// the iterator.
template <typename Cache, typename UnderlyingIterator>
class BaseUnorderedIterator
: public BaseForBaseUnorderedIterator<Cache, UnderlyingIterator> {
protected:
using super = BaseForBaseUnorderedIterator<Cache, UnderlyingIterator>;
using PRIVATE_BASE_ITERATOR_MEMBERS;
// These are the key and value types the BaseIterator extracts
using Key = typename super::KeyType;
using Value = typename super::ValueType;
public:
using Tag = LRU::Tag::UnorderedIterator;
using PUBLIC_BASE_ITERATOR_MEMBERS;
/// Constructor.
BaseUnorderedIterator() noexcept = default;
/// \copydoc BaseIterator::BaseIterator(Cache,UnderlyingIterator)
explicit BaseUnorderedIterator(Cache& cache,
const UnderlyingIterator& iterator) noexcept
: super(cache, iterator) {
}
/// Generalized copy constructor.
///
/// Useful mainly for non-const to const conversion.
///
/// \param other The iterator to copy from.
template <typename AnyCache, typename AnyUnderlyingIterator>
BaseUnorderedIterator(
const BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>&
other) noexcept
: super(other) {
}
/// Copy constructor.
BaseUnorderedIterator(const BaseUnorderedIterator& other) noexcept = default;
/// Move constructor.
BaseUnorderedIterator(BaseUnorderedIterator&& other) noexcept = default;
/// Copy assignment operator.
BaseUnorderedIterator&
operator=(const BaseUnorderedIterator& other) noexcept = default;
/// Move assignment operator.
template <typename AnyCache, typename AnyUnderlyingIterator>
BaseUnorderedIterator&
operator=(BaseUnorderedIterator<AnyCache, AnyUnderlyingIterator>
unordered_iterator) noexcept {
swap(unordered_iterator);
return *this;
}
/// Destructor.
virtual ~BaseUnorderedIterator() = default;
/// Compares this iterator for equality with another unordered iterator.
///
/// \param other Another unordered iterator.
/// \returns True if both iterators point to the same entry, else false.
template <typename AnyCache, typename AnyIterator>
bool
operator==(const BaseUnorderedIterator<AnyCache, AnyIterator>& other) const
noexcept {
return this->_iterator == other._iterator;
}
/// Compares this iterator for inequality with another unordered iterator.
///
/// \param other Another unordered iterator.
/// \returns True if the iterators point to different entries, else false.
template <typename AnyCache, typename AnyIterator>
bool
operator!=(const BaseUnorderedIterator<AnyCache, AnyIterator>& other) const
noexcept {
return !(*this == other);
}
/// 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.
BaseUnorderedIterator& 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.
BaseUnorderedIterator operator++(int) {
auto previous = *this;
++*this;
return previous;
}
/// \copydoc BaseIterator::operator*
/// \details If the iterator is invalid, behavior is undefined. No exception
/// handling is performed.
Entry& operator*() noexcept override {
if (!_entry.has_value()) {
_entry.emplace(_iterator->first, _iterator->second.value);
}
return *_entry;
}
/// \returns A reference to the entry the iterator points to.
/// \throws LRU::Error::InvalidIterator if the iterator is the end iterator.
/// \throws LRU::Error::KeyExpired if the key pointed to by the iterator has
/// expired.
Entry& entry() override {
if (!_entry.has_value()) {
_entry.emplace(_iterator->first, _iterator->second.value);
}
_cache->throw_if_invalid(*this);
return *_entry;
}
/// \returns A reference to the key the iterator points to.
/// \throws LRU::Error::InvalidIterator if the iterator is the end iterator.
/// \throws LRU::Error::KeyExpired if the key pointed to by the iterator has
/// expired.
const Key& key() override {
return entry().key();
}
/// \returns A reference to the value the iterator points to.
/// \throws LRU::Error::InvalidIterator if the iterator is the end iterator.
/// \throws LRU::Error::KeyExpired if the key pointed to by the iterator has
/// expired.
Value& value() override {
return entry().value();
}
protected:
template <typename, typename, typename>
friend class BaseOrderedIterator;
template <typename, typename, typename, typename, typename>
friend class LRU::TimedCache;
};
} // namespace Internal
} // namespace LRU
#endif // BASE_UNORDERED_ITERATOR_HPP

View File

@@ -0,0 +1,159 @@
/// 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 LRU_INTERNAL_CALLBACK_MANAGER_HPP
#define LRU_INTERNAL_CALLBACK_MANAGER_HPP
#include <functional>
#include <vector>
#include <lru/entry.hpp>
#include <lru/internal/optional.hpp>
namespace LRU {
namespace Internal {
/// Manages hit, miss and access callbacks for a cache.
///
/// The callback manager implements the "publisher" of the observer pattern we
/// implement. It stores and calls three kinds of callbacks:
/// 1. Hit callbacks, taking a key and value after a cache hit.
/// 2. Miss callbacks, taking only a key, that was not found in a cache.
/// 3. Access callbacks, taking a key and a boolean indicating a hit or a miss.
///
/// Callbacks can be added, accessed and cleared.
template <typename Key, typename Value>
class CallbackManager {
public:
using HitCallback = std::function<void(const Key&, const Value&)>;
using MissCallback = std::function<void(const Key&)>;
using AccessCallback = std::function<void(const Key&, bool)>;
using HitCallbackContainer = std::vector<HitCallback>;
using MissCallbackContainer = std::vector<MissCallback>;
using AccessCallbackContainer = std::vector<AccessCallback>;
/// Calls all callbacks registered for a hit, with the given key and value.
///
/// \param key The key for which a cache hit ocurred.
/// \param value The value that was found for the key.
void hit(const Key& key, const Value& value) {
_call_each(_hit_callbacks, key, value);
_call_each(_access_callbacks, key, true);
}
/// Calls all callbacks registered for a miss, with the given key.
///
/// \param key The key for which a cache miss ocurred.
void miss(const Key& key) {
_call_each(_miss_callbacks, key);
_call_each(_access_callbacks, key, false);
}
/// Registers a new hit callback.
///
/// \param hit_callback The hit callback function to register with the
/// manager.
template <typename Callback>
void hit_callback(Callback&& hit_callback) {
_hit_callbacks.emplace_back(std::forward<Callback>(hit_callback));
}
/// Registers a new miss callback.
///
/// \param miss_callback The miss callback function to register with the
/// manager.
template <typename Callback>
void miss_callback(Callback&& miss_callback) {
_miss_callbacks.emplace_back(std::forward<Callback>(miss_callback));
}
/// Registers a new access callback.
///
/// \param access_callback The access callback function to register with the
/// manager.
template <typename Callback>
void access_callback(Callback&& access_callback) {
_access_callbacks.emplace_back(std::forward<Callback>(access_callback));
}
/// Clears all hit callbacks.
void clear_hit_callbacks() {
_hit_callbacks.clear();
}
/// Clears all miss callbacks.
void clear_miss_callbacks() {
_miss_callbacks.clear();
}
/// Clears all access callbacks.
void clear_access_callbacks() {
_access_callbacks.clear();
}
/// Clears all callbacks.
void clear() {
clear_hit_callbacks();
clear_miss_callbacks();
clear_access_callbacks();
}
/// \returns All hit callbacks.
const HitCallbackContainer& hit_callbacks() const noexcept {
return _hit_callbacks;
}
/// \returns All miss callbacks.
const MissCallbackContainer& miss_callbacks() const noexcept {
return _miss_callbacks;
}
/// \returns All access callbacks.
const AccessCallbackContainer& access_callbacks() const noexcept {
return _access_callbacks;
}
private:
/// Calls each function in the given container with the given arguments.
///
/// \param callbacks The container of callbacks to call.
/// \param args The arguments to call the callbacks with.
template <typename CallbackContainer, typename... Args>
void _call_each(const CallbackContainer& callbacks, Args&&... args) {
for (const auto& callback : callbacks) {
callback(std::forward<Args>(args)...);
}
}
/// The container of hit callbacks registered.
HitCallbackContainer _hit_callbacks;
/// The container of miss callbacks registered.
MissCallbackContainer _miss_callbacks;
/// The container of access callbacks registered.
AccessCallbackContainer _access_callbacks;
};
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_CALLBACK_MANAGER_HPP

View File

@@ -0,0 +1,88 @@
/// 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 LRU_INTERNAL_DEFINITIONS_HPP
#define LRU_INTERNAL_DEFINITIONS_HPP
#include <chrono>
#include <cstddef>
#include <functional>
#include <list>
#include <tuple>
#include <unordered_map>
namespace LRU {
namespace Internal {
/// The default capacity for all caches.
const std::size_t DEFAULT_CAPACITY = 128;
/// The reference type use to store keys in the order queue.
template <typename T>
using Reference = std::reference_wrapper<T>;
/// Compares two References for equality.
///
/// This is necessary because `std::reference_wrapper` does not define any
/// operator overloads. We do need them, however (e.g. for container
/// comparison).
///
/// \param first The first reference to compare.
/// \param second The second reference to compare.
template <typename T, typename U>
bool operator==(const Reference<T>& first, const Reference<U>& second) {
return first.get() == second.get();
}
/// Compares two References for inequality.
///
/// This is necessary because `std::reference_wrapper` does not define any
/// operator overloads. We do need them, however (e.g. for container
/// comparison).
///
/// \param first The first reference to compare.
/// \param second The second reference to compare.
template <typename T, typename U>
bool operator!=(const Reference<T>& first, const Reference<U>& second) {
return !(first == second);
}
/// The default queue type used internally.
template <typename T>
using Queue = std::list<Reference<T>>;
/// The default map type used internally.
template <typename Key,
typename Information,
typename HashFunction,
typename KeyEqual>
using Map = std::unordered_map<Key, Information, HashFunction, KeyEqual>;
/// The default clock used internally.
using Clock = std::chrono::steady_clock;
/// The default timestamp (time point) used internally.
using Timestamp = Clock::time_point;
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_DEFINITIONS_HPP

View File

@@ -0,0 +1,62 @@
/// 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 LRU_INTERNAL_HASH_HPP
#define LRU_INTERNAL_HASH_HPP
#include <cstddef>
#include <functional>
#include <tuple>
/// `std::hash` specialization to allow storing tuples as keys
/// in `std::unordered_map`.
///
/// Essentially hashes all tuple elements and jumbles the
/// individual hashes together.
namespace std {
template <typename... Ts>
struct hash<std::tuple<Ts...>> {
using argument_type = std::tuple<Ts...>;
using result_type = std::size_t;
result_type operator()(const argument_type& argument) const {
return hash_tuple(argument, std::make_index_sequence<sizeof...(Ts)>());
}
private:
template <std::size_t I, std::size_t... Is>
result_type
hash_tuple(const argument_type& tuple, std::index_sequence<I, Is...>) const {
auto value = std::get<I>(tuple);
auto current = std::hash<decltype(value)>{}(value);
auto seed = hash_tuple(tuple, std::index_sequence<Is...>());
// http://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html
return current + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
result_type hash_tuple(const argument_type&, std::index_sequence<>) const {
return 0;
}
};
} // namespace std
#endif // LRU_INTERNAL_HASH_HPP

View File

@@ -0,0 +1,145 @@
/// 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 LRU_INTERNAL_INFORMATION_HPP
#define LRU_INTERNAL_INFORMATION_HPP
#include <cstddef>
#include <tuple>
#include <utility>
#include <lru/internal/definitions.hpp>
#include <lru/internal/utility.hpp>
namespace LRU {
namespace Internal {
/// The value type of internal maps, used to store a value and iterator.
///
/// This information object is the basis of an LRU cache, which must associated
/// a value and such an order iterator with a key, such that the iterator may be
/// moved to the front of the order when the key is updated with a new value.
///
/// \tparam Key The key type of the information.
/// \tparam Value The value type of the information.
template <typename Key, typename Value>
struct Information {
using KeyType = Key;
using ValueType = Value;
using QueueIterator = typename Internal::Queue<const Key>::const_iterator;
/// Constructor.
///
/// \param value_ The value for the information.
/// \param order_ The order iterator for the information.
explicit Information(const Value& value_,
QueueIterator order_ = QueueIterator())
: value(value_), order(order_) {
}
/// Constructor.
///
/// \param order_ The order iterator for the information.
/// \param value_arguments Any number of arguments to perfectly forward to the
/// value type's constructor.
// template <typename... ValueArguments>
// Information(QueueIterator order_, ValueArguments&&... value_arguments)
// : value(std::forward<ValueArguments>(value_arguments)...), order(order_) {
// }
/// Constructor.
///
/// \param order_ The order iterator for the information.
/// \param value_arguments A tuple of arguments to perfectly forward to the
/// value type's constructor.
///
template <typename... ValueArguments>
explicit Information(const std::tuple<ValueArguments...>& value_arguments,
QueueIterator order_ = QueueIterator())
: Information(
order_, value_arguments, Internal::tuple_indices(value_arguments)) {
}
/// Copy constructor.
Information(const Information& other) = default;
/// Move constructor.
Information(Information&& other) = default;
/// Copy assignment operator.
Information& operator=(const Information& other) = default;
/// Move assignment operator.
Information& operator=(Information&& other) = default;
/// Destructor.
virtual ~Information() = default;
/// Compares the information for equality with another information object.
///
/// \param other The other information object to compare to.
/// \returns True if key and value (not the iterator itself) of the two
/// information objects are equal, else false.
virtual bool operator==(const Information& other) const noexcept {
if (this == &other) return true;
if (this->value != other.value) return false;
// We do not compare the iterator (because otherwise two containers
// holding information would never be equal). We also do not compare
// the key stored in the iterator, because keys will always have been
// compared before this operator is called.
return true;
}
/// Compares the information for inequality with another information object.
///
/// \param other The other information object to compare for.
/// \returns True if key and value (not the iterator itself) of the two
/// information objects are unequal, else false.
virtual bool operator!=(const Information& other) const noexcept {
return !(*this == other);
}
/// The value of the information.
Value value;
/// The order iterator of the information.
QueueIterator order;
private:
/// Implementation for the constructor taking a tuple of arguments for the
/// value.
///
/// \param order_ The order iterator for the information.
/// \param value_argument The tuple of arguments to perfectly forward to the
/// value type's constructor.
/// \param _ An index sequence to access the elements of the tuple
template <typename... ValueArguments, std::size_t... Indices>
Information(const QueueIterator& order_,
const std::tuple<ValueArguments...>& value_argument,
std::index_sequence<Indices...> _)
: value(std::forward<ValueArguments>(std::get<Indices>(value_argument))...)
, order(order_) {
}
};
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_INFORMATION_HPP

View File

@@ -0,0 +1,254 @@
/// 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 LRU_INTERNAL_LAST_ACCESSED_HPP
#define LRU_INTERNAL_LAST_ACCESSED_HPP
#include <algorithm>
#include <functional>
#include <iterator>
#include <lru/internal/utility.hpp>
namespace LRU {
namespace Internal {
/// Provides a simple iterator-compatible pointer object for a key and
/// information.
///
/// The easisest idea for this class, theoretically, would be to just store an s
/// iterator to the internal cache map (i.e. template the class on the iterator
/// type). However, the major trouble with that approach is that this class
/// should be 100% *mutable*, as in "always non-const", so that keys and
/// informations
/// we store for fast access can be (quickly) retrieved as either const or
/// non-const (iterators for example). This is not possible, since the
/// const-ness of `const_iterators` are not the usual idea of const in C++,
/// meaning especially it cannot be cast away with a `const_cast` as is required
/// for the mutability. As such, we *must* store the plain keys and
/// informations.
/// This, however, means that iterators cannot be stored efficiently, since a
/// new hash table lookup would be required to go from a key to its iterator.
/// However, since the main use case of this class is to avoid a second lookup
/// in the usual `if (cache.contains(key)) return cache.lookup(key)`, which is
/// not an issue for iterators since they can be compared to the `end` iterator
/// in constant time (equivalent to the call to `contains()`).
///
/// WARNING: This class stores *pointers* to keys and informations. As such
/// lifetime
/// of the pointed-to objects must be cared for by the user of this class.
///
/// \tparam Key The type of key being accessed.
/// \tparam InformationType The type of information being accessed.
/// \tparam KeyEqual The type of the key comparison function.
template <typename Key,
typename InformationType,
typename KeyEqual = std::equal_to<Key>>
class LastAccessed {
public:
/// Constructor.
///
/// \param key_equal The function to compare keys with.
explicit LastAccessed(const KeyEqual& key_equal = KeyEqual())
: _key(nullptr)
, _information(nullptr)
, _is_valid(false)
, _key_equal(key_equal) {
}
/// Constructor.
///
/// \param key The key to store a reference to.
/// \param information The information to store a reference to.
/// \param key_equal The function to compare keys with.
LastAccessed(const Key& key,
const InformationType& information,
const KeyEqual& key_equal = KeyEqual())
: _key(const_cast<Key*>(&key))
, _information(const_cast<InformationType*>(&information))
, _is_valid(true)
, _key_equal(key_equal) {
}
/// Constructor.
///
/// \param iterator An iterator pointing to a key and information to use for
/// constructing the instance.
/// \param key_equal The function to compare keys with.
template <typename Iterator>
explicit LastAccessed(Iterator iterator,
const KeyEqual& key_equal = KeyEqual())
: LastAccessed(iterator->first, iterator->second, key_equal) {
}
/// Copy assignment operator for iterators.
///
/// \param iterator An iterator pointing to a key and value to use for the
/// `LastAccessed` instance.
/// \return The resulting `LastAccessed` instance.
template <typename Iterator>
LastAccessed& operator=(Iterator iterator) {
_key = const_cast<Key*>(&(iterator->first));
_information = const_cast<InformationType*>(&(iterator->second));
_is_valid = true;
return *this;
}
/// Compares a `LastAccessed` object for equality with a key.
///
/// \param last_accessed The `LastAccessed` instance to compare.
/// \param key The key instance to compare.
/// \returns True if the key of the `LastAccessed` object's key equals the
/// given key, else false.
friend bool
operator==(const LastAccessed& last_accessed, const Key& key) noexcept {
if (!last_accessed._is_valid) return false;
return last_accessed._key_equal(key, last_accessed.key());
}
/// \copydoc operator==(const LastAccessed&,const Key&)
friend bool
operator==(const Key& key, const LastAccessed& last_accessed) noexcept {
return last_accessed == key;
}
/// Compares a `LastAccessed` object for equality with an iterator.
///
/// \param last_accessed The `LastAccessed` instance to compare.
/// \param iterator The iterator to compare with.
/// \returns True if the `LastAccessed` object's key equals that of the
/// iterator, else false.
template <typename Iterator, typename = enable_if_iterator<Iterator>>
friend bool
operator==(const LastAccessed& last_accessed, Iterator iterator) noexcept {
/// Fast comparisons to an iterator (not relying on implicit conversion)
return last_accessed == iterator->first;
}
/// \copydoc operator==(const LastAccessed&,Iterator)
template <typename Iterator, typename = enable_if_iterator<Iterator>>
friend bool
operator==(Iterator iterator, const LastAccessed& last_accessed) noexcept {
return last_accessed == iterator;
}
/// Compares a `LastAccessed` object for inequality with something.
///
/// \param last_accessed The `LastAccessed` instance to compare.
/// \param other Something else to compare to.
/// \returns True if the key of the `LastAccessed` object's key does not equal
/// the given other object's key, else false.
template <typename T>
friend bool
operator!=(const LastAccessed& last_accessed, const T& other) noexcept {
return !(last_accessed == other);
}
/// \copydoc operator!=(const LastAccessed&,const T&)
template <typename T>
friend bool
operator!=(const T& other, const LastAccessed& last_accessed) noexcept {
return !(other == last_accessed);
}
/// \returns The last accessed key.
Key& key() noexcept {
assert(is_valid());
return *_key;
}
/// \returns The last accessed key.
const Key& key() const noexcept {
assert(is_valid());
return *_key;
}
/// \returns The last accessed information.
InformationType& information() noexcept {
assert(is_valid());
return *_information;
}
/// \returns The last accessed information.
const InformationType& information() const noexcept {
assert(is_valid());
return *_information;
}
/// \returns The last accessed information.
auto& iterator() noexcept {
assert(is_valid());
return _information->order;
}
/// \returns The last accessed value.
auto& value() noexcept {
assert(is_valid());
return _information->value;
}
/// \returns The last accessed value.
const auto& value() const noexcept {
assert(is_valid());
return _information->value;
}
/// \returns True if the key and information of the instance may be accessed,
/// else false.
bool is_valid() const noexcept {
return _is_valid;
}
/// \copydoc is_valid()
explicit operator bool() const noexcept {
return is_valid();
}
/// Invalidates the instance.
void invalidate() noexcept {
_is_valid = false;
_key = nullptr;
_information = nullptr;
}
/// \returns The key comparison function used.
const KeyEqual& key_equal() const noexcept {
return _key_equal;
}
private:
/// A pointer to the key that was last accessed (if any).
Key* _key;
/// A pointer to the information that was last accessed (if any).
InformationType* _information;
/// True if the key and information pointers are valid, else false.
bool _is_valid;
/// The function used to compare keys.
KeyEqual _key_equal;
};
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_LAST_ACCESSED_HPP

View File

@@ -0,0 +1,207 @@
/// 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 LRU_INTERNAL_OPTIONAL_HPP
#define LRU_INTERNAL_OPTIONAL_HPP
#ifndef __has_include
#define USE_LRU_OPTIONAL
#elif __has_include(<optional>)
#include <optional>
namespace LRU {
namespace Internal {
template <typename T>
using Optional = std::optional<T>;
} // namespace Internal
} // namespace LRU
#else
#define USE_LRU_OPTIONAL
#endif
#ifdef USE_LRU_OPTIONAL
#include <memory>
#include <stdexcept>
namespace LRU {
namespace Internal {
// A roll-your-own replacement of `std::optional`.
//
// This class is only to be used if `std::optional` is unavailable. It
// implements an optional type simply on top of a `unique_ptr`. It is
// API-compatible with `std::optional`, as required for our purposes.
template <typename T>
class Optional {
public:
/// Constructor.
Optional() = default;
/// Copy constructor.
///
/// \param other The other optional object to copy from.
Optional(const Optional& other) {
if (other) emplace(*other);
}
/// Generalized copy constructor.
///
/// \param other The other optional object to copy from.
template <typename U,
typename = std::enable_if_t<std::is_convertible<T, U>::value>>
Optional(const Optional<U>& other) {
if (other) emplace(*other);
}
/// Move constructor.
///
/// \param other The other optional object to move into this one.
Optional(Optional&& other) noexcept {
swap(other);
}
/// Generalized move constructor.
///
/// \param other The other optional object to move into this one.
template <typename U,
typename = std::enable_if_t<std::is_convertible<T, U>::value>>
Optional(Optional<U>&& other) noexcept {
if (other) {
_value = std::make_unique<T>(std::move(*other));
}
}
/// Assignment operator.
///
/// \param other The other object to assign from.
/// \returns The resulting optional instance.
Optional& operator=(Optional other) noexcept {
swap(other);
return *this;
}
/// Swaps the contents of this optional with another one.
///
/// \param other The other optional to swap with.
void swap(Optional& other) {
_value.swap(other._value);
}
/// Swaps the contents of two optionals.
///
/// \param first The first optional to swap.
/// \param second The second optional to swap.
friend void swap(Optional& first, Optional& second) /* NOLINT */ {
first.swap(second);
}
/// \returns True if the `Optional` has a value, else false.
bool has_value() const noexcept {
return static_cast<bool>(_value);
}
/// \copydoc has_value()
explicit operator bool() const noexcept {
return has_value();
}
/// \returns A pointer to the current value. Behavior is undefined if the
/// optional has no value.
T* operator->() {
return _value.get();
}
/// \returns A const pointer to the current value. Behavior is undefined if
/// the `Optional` has no value.
const T* operator->() const {
return _value.get();
}
/// \returns A const reference to the current value. Behavior is undefined if
/// the `Optional` has no value.
const T& operator*() const {
return *_value;
}
/// \returns A reference to the current value. Behavior is undefined if
/// the `Optional` has no value.
T& operator*() {
return *_value;
}
/// \returns A reference to the current value.
/// \throws std::runtime_error If the `Optional` currently has no value.
T& value() {
if (!has_value()) {
// Actually std::bad_optional_access
throw std::runtime_error("optional has no value");
}
return *_value;
}
/// \returns A const reference to the current value.
/// \throws std::runtime_error If the `Optional` currently has no value.
const T& value() const {
if (!has_value()) {
// Actually std::bad_optional_access
throw std::runtime_error("optional has no value");
}
return *_value;
}
/// \returns The current value, or the given argument if there is no value.
/// \param default_value The value to return if this `Optional` currently has
/// no value.
template <class U>
T value_or(U&& default_value) const {
return *this ? **this : static_cast<T>(std::forward<U>(default_value));
}
/// Resets the `Optional` to have no value.
void reset() noexcept {
_value.reset();
}
/// Constructs the `Optional`'s value with the given arguments.
///
/// \param args Arguments to perfeclty forward to the value's constructor.
template <typename... Args>
void emplace(Args&&... args) {
_value = std::make_unique<T>(std::forward<Args>(args)...);
}
private:
template <typename>
friend class Optional;
/// The value, as we implement it.
std::unique_ptr<T> _value;
};
} // namespace Internal
} // namespace LRU
#endif
#endif // LRU_INTERNAL_OPTIONAL_HPP

View File

@@ -0,0 +1,150 @@
/// 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 LRU_STATISTICS_MUTATOR_HPP
#define LRU_STATISTICS_MUTATOR_HPP
#include <cassert>
#include <cstddef>
#include <memory>
#include <utility>
#include <lru/internal/optional.hpp>
#include <lru/statistics.hpp>
namespace LRU {
namespace Internal {
/// A mutable proxy interface to a statistics object.
///
/// The `StatisticsMutator` allows modification of the members of a statistics
/// object via a narrow interface, available only to internal classes. The point
/// of this is that while we don't want the user to be able to modify the hit or
/// miss count on a statistics object (it is "getter-only" in that sense), it's
/// also not ideal, from an encapsulation standpoint, to make the cache classes
/// (which do need to access and modify the hit and miss counts) friends of the
/// statistics. This is especially true since the caches should only need to
/// register hits or misses and not have to increment the count of total
/// accesses. As such, we really require a "package-level" interface that is not
/// visible to the end user, while at the same time providing an interface to
/// internal classes. The `StatisticsMutator` is a proxy/adapter class that
/// serves exactly this purpose. It is friends with the `Statistics` and can
/// thus access its members. At the same time the interface it defines is narrow
/// and provides only the necessary interface for the cache classes to register
/// hits and misses.
template <typename Key>
class StatisticsMutator {
public:
using StatisticsPointer = std::shared_ptr<Statistics<Key>>;
/// Constructor.
StatisticsMutator() noexcept = default;
/// Constructor.
///
/// \param stats A shared pointer lvalue reference.
StatisticsMutator(const StatisticsPointer& stats) // NOLINT(runtime/explicit)
: _stats(stats) {
}
/// Constructor.
///
/// \param stats A shared pointer rvalue reference to move into the
/// mutator.
StatisticsMutator(StatisticsPointer&& stats) // NOLINT(runtime/explicit)
: _stats(std::move(stats)) {
}
/// Registers a hit for the given key with the internal statistics.
///
/// \param key The key to register a hit for.
void register_hit(const Key& key) {
assert(has_stats());
_stats->_total_accesses += 1;
_stats->_total_hits += 1;
auto iterator = _stats->_key_map.find(key);
if (iterator != _stats->_key_map.end()) {
iterator->second.hits += 1;
}
}
/// Registers a miss for the given key with the internal statistics.
///
/// \param key The key to register a miss for.
void register_miss(const Key& key) {
assert(has_stats());
_stats->_total_accesses += 1;
auto iterator = _stats->_key_map.find(key);
if (iterator != _stats->_key_map.end()) {
iterator->second.misses += 1;
}
}
/// \returns A reference to the statistics object.
Statistics<Key>& get() noexcept {
assert(has_stats());
return *_stats;
}
/// \returns A const reference to the statistics object.
const Statistics<Key>& get() const noexcept {
assert(has_stats());
return *_stats;
}
/// \returns A `shared_ptr` to the statistics object.
StatisticsPointer& shared() noexcept {
return _stats;
}
/// \returns A const `shared_ptr` to the statistics object.
const StatisticsPointer& shared() const noexcept {
return _stats;
}
/// \returns True if the mutator has a statistics object, else false.
bool has_stats() const noexcept {
return _stats != nullptr;
}
/// \copydoc has_stats()
explicit operator bool() const noexcept {
return has_stats();
}
/// Resets the internal statistics pointer.
void reset() {
_stats.reset();
}
private:
/// A shared pointer to a statistics object.
std::shared_ptr<Statistics<Key>> _stats;
};
} // namespace Internal
} // namespace LRU
#endif // LRU_STATISTICS_MUTATOR_HPP

View File

@@ -0,0 +1,116 @@
/// 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 LRU_INTERNAL_TIMED_INFORMATION_HPP
#define LRU_INTERNAL_TIMED_INFORMATION_HPP
#include <cstddef>
#include <tuple>
#include <utility>
#include <lru/internal/definitions.hpp>
#include <lru/internal/information.hpp>
#include <lru/internal/utility.hpp>
namespace LRU {
namespace Internal {
/// The information object for timed caches.
///
/// TimedInformation differs from plain information only in that it stores the
/// creation time, to know when a key has expired.
///
/// \tparam Key The key type of the information.
/// \tparam Value The value type of the information.
template <typename Key, typename Value>
struct TimedInformation : public Information<Key, Value> {
using super = Information<Key, Value>;
using typename super::QueueIterator;
using Timestamp = Internal::Timestamp;
/// Constructor.
///
/// \param value_ The value for the information.
/// \param insertion_time_ The insertion timestamp of the key.
/// \param order_ The order iterator for the information.
TimedInformation(const Value& value_,
const Timestamp& insertion_time_,
QueueIterator order_ = QueueIterator())
: super(value_, order_), insertion_time(insertion_time_) {
}
/// Constructor.
///
/// Uses the current time as the insertion timestamp.
///
/// \param value_ The value for the information.
/// \param order_ The order iterator for the information.
explicit TimedInformation(const Value& value_,
QueueIterator order_ = QueueIterator())
: TimedInformation(value_, Internal::Clock::now(), order_) {
}
/// \copydoc Information::Information(QueueIterator,ValueArguments&&)
template <typename... ValueArguments>
TimedInformation(QueueIterator order_, ValueArguments&&... value_argument)
: super(std::forward<ValueArguments>(value_argument)..., order_)
, insertion_time(Internal::Clock::now()) {
}
/// \copydoc Information::Information(QueueIterator,const
/// std::tuple<ValueArguments...>&)
template <typename... ValueArguments>
explicit TimedInformation(
const std::tuple<ValueArguments...>& value_arguments,
QueueIterator order_ = QueueIterator())
: super(value_arguments, order_), insertion_time(Internal::Clock::now()) {
}
/// Compares this timed information for equality with another one.
///
/// Additionally to key and value equality, the timed information requires
/// that the insertion timestamps be equal.
///
/// \param other The other timed information.
/// \returns True if this information equals the other one, else false.
bool operator==(const TimedInformation& other) const noexcept {
if (super::operator!=(other)) return false;
return this->insertion_time == other.insertion_time;
}
/// Compares this timed information for inequality with another one.
///
/// \param other The other timed information.
/// \returns True if this information does not equal the other one, else
/// false.
/// \see operator==()
bool operator!=(const TimedInformation& other) const noexcept {
return !(*this == other);
}
/// The time at which the key of the information was insterted into a cache.
const Timestamp insertion_time;
};
} // namespace Internal
} // namespace LRU
#endif // LRU_INTERNAL_TIMED_INFORMATION_HPP

View File

@@ -0,0 +1,178 @@
/// 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 LRU_UTILITY_HPP
#define LRU_UTILITY_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <utility>
namespace LRU {
namespace Internal {
/// Generates an index sequence for a tuple.
///
/// \tparam Ts The types of the tuple (to deduce the size).
template <typename... Ts>
constexpr auto tuple_indices(const std::tuple<Ts...>&) {
return std::make_index_sequence<sizeof...(Ts)>();
}
/// Applies (in the functional sense) a tuple to the constructor of a class.
///
/// \tparam T The type to construct.
/// \tparam Indices The indices into the tuple (generated from an index
/// sequence).
/// \param args The tuple of arguments to construct the object with.
template <typename T, typename... Args, std::size_t... Indices>
constexpr T construct_from_tuple(const std::tuple<Args...>& arguments,
std::index_sequence<Indices...>) {
return T(std::forward<Args>(std::get<Indices>(arguments))...);
}
/// Applies (in the functional sense) a tuple to the constructor of a class.
///
/// \tparam T The type to construct.
/// \param args The tuple of arguments to construct the object with.
template <typename T, typename... Args>
constexpr T construct_from_tuple(const std::tuple<Args...>& args) {
return construct_from_tuple<T>(args, tuple_indices(args));
}
/// Applies (in the functional sense) a tuple to the constructor of a class.
///
/// \tparam T The type to construct.
/// \param args The tuple of arguments to construct the object with.
template <typename T, typename... Args>
constexpr T construct_from_tuple(std::tuple<Args...>&& args) {
return construct_from_tuple<T>(std::move(args), tuple_indices(args));
}
/// A type trait that disables a template overload if a type is not an iterator.
///
/// \tparam T the type to check.
template <typename T>
using enable_if_iterator = typename std::iterator_traits<T>::value_type;
/// A type trait that disables a template overload if a type is not a range.
///
/// \tparam T the type to check.
template <typename T>
using enable_if_range = std::pair<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>;
/// A type trait that disables a template overload if a type is not an iterator
/// over a pair.
///
/// \tparam T the type to check.
template <typename T>
using enable_if_iterator_over_pair =
std::pair<typename std::iterator_traits<T>::value_type::first_type,
typename std::iterator_traits<T>::value_type::first_type>;
/// A type trait that disables a template overload if a type is not convertible
/// to a target type.
///
/// \tparam Target The type one wants to check against.
/// \tparam T The type to check.
template <typename Target, typename T>
using enable_if_same = std::enable_if_t<std::is_convertible<T, Target>::value>;
/// Base case for `static_all_of` (the neutral element of AND is true).
constexpr bool static_all_of() noexcept {
return true;
}
/// Checks if all the given parameters evaluate to true.
///
/// \param head The first expression to check.
/// \param tail The remaining expression to check.
template <typename Head, typename... Tail>
constexpr bool static_all_of(Head&& head, Tail&&... tail) {
// Replace with (ts && ...) when the time is right
return std::forward<Head>(head) && static_all_of(std::forward<Tail>(tail)...);
}
/// Base case for `static_any_of` (the neutral element of OR is false).
constexpr bool static_any_of() noexcept {
return false;
}
/// Checks if any the given parameters evaluate to true.
///
/// \param head The first expression to check.
/// \param tail The remaining expression to check.
/// \returns True if any of the given parameters evaluate to true.
template <typename Head, typename... Tail>
constexpr bool static_any_of(Head&& head, Tail&&... tail) {
// Replace with (ts || ...) when the time is right
return std::forward<Head>(head) || static_any_of(std::forward<Tail>(tail)...);
}
/// Checks if none the given parameters evaluate to true.
///
/// \param ts The expressions to check.
/// \returns True if any of the given parameters evaluate to true.
template <typename... Ts>
constexpr bool static_none_of(Ts&&... ts) {
// Replace with (!ts && ...) when the time is right
return !static_any_of(std::forward<Ts>(ts)...);
}
/// Checks if all the given types are convertible to the first type.
///
/// \tparam T the first type.
/// \tparam Ts The types to check against the first.
template <typename T, typename... Ts>
constexpr bool
all_of_type = static_all_of(std::is_convertible<Ts, T>::value...);
/// Checks if none of the given types are convertible to the first type.
///
/// \tparam T the first type.
/// \tparam Ts The types to check against the first.
template <typename T, typename... Ts>
constexpr bool
none_of_type = static_none_of(std::is_convertible<Ts, T>::value...);
/// Base case for `for_each`.
template <typename Function>
void for_each(Function) noexcept {
}
/// Calls a function for each of the given variadic arguments.
///
/// \param function The function to call for each argument.
/// \param head The first value to call the function with.
/// \param tail The remaining values to call the function with.
template <typename Function, typename Head, typename... Tail>
void for_each(Function function, Head&& head, Tail&&... tail) {
function(std::forward<Head>(head));
for_each(function, std::forward<Tail>(tail)...);
}
} // namespace Internal
} // namespace LRU
#endif // LRU_UTILITY_HPP

View File

@@ -0,0 +1,40 @@
/// 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 LRU_ITERATOR_TAGS_HPP
#define LRU_ITERATOR_TAGS_HPP
namespace LRU {
namespace Tag {
struct OrderedIterator {};
struct UnorderedIterator {};
} // namespace Tag
namespace Lowercase {
namespace tag {
using ordered_iterator = ::LRU::Tag::OrderedIterator;
using unordered_iterator = ::LRU::Tag::UnorderedIterator;
} // namespace tag
} // namespace Lowercase
} // namespace LRU
#endif // LRU_ITERATOR_TAGS_HPP

View File

@@ -0,0 +1,67 @@
/// 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 LRU_KEY_STATISTICS_HPP
#define LRU_KEY_STATISTICS_HPP
#include <cstddef>
namespace LRU {
/// Stores statistics for a single key.
///
/// The statistics stored are the total number of hits and the total number of
/// misses. The total number of acccesses (the sum of hits and misses) may be
/// accessed as well.
struct KeyStatistics {
using size_t = std::size_t;
/// Constructor.
///
/// \param hits_ The initial number of hits for the key.
/// \param misses_ The initial number of misses for the key.
explicit KeyStatistics(size_t hits_ = 0, size_t misses_ = 0)
: hits(hits_), misses(misses_) {
}
/// \returns The total number of accesses made for the key.
/// \details This is the sum of the hits and misses.
size_t accesses() const noexcept {
return hits + misses;
}
/// Resets the statistics for a key (sets them to zero).
void reset() {
hits = 0;
misses = 0;
}
/// The number of hits for the key.
size_t hits;
/// The number of misses for the key.
size_t misses;
};
} // namespace LRU
#endif // LRU_KEY_STATISTICS_HPP

View File

@@ -0,0 +1,34 @@
/// 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 LRU_LOWERCASE_HPP
#define LRU_LOWERCASE_HPP
#include <lru/lru.hpp>
namespace LRU {
using namespace Lowercase; // NOLINT(build/namespaces)
} // namespace LRU
namespace lru = LRU;
#endif // LRU_LOWERCASE_HPP

View File

@@ -0,0 +1,33 @@
/// 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 LRU_HPP
#define LRU_HPP
#include <lru/cache-tags.hpp>
#include <lru/cache.hpp>
#include <lru/error.hpp>
#include <lru/iterator-tags.hpp>
#include <lru/statistics.hpp>
#include <lru/timed-cache.hpp>
#include <lru/wrap.hpp>
#endif // LRU_HPP

View File

@@ -0,0 +1,256 @@
/// 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 LRU_STATISTICS_HPP
#define LRU_STATISTICS_HPP
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <lru/error.hpp>
#include <lru/internal/utility.hpp>
#include <lru/key-statistics.hpp>
namespace LRU {
namespace Internal {
template <typename>
class StatisticsMutator;
}
/// Stores statistics about LRU cache utilization and efficiency.
///
/// The statistics object stores the number of misses and hits were recorded for
/// a cache in total. Furthemore, it is possibly to register a number of keys
/// for *monitoring*. For each of these keys, an additional hit and miss count
/// is maintained, that can keep insight into the utiliization of a particular
/// cache. Note that accesses only mean lookups -- insertions or erasures will
/// never signify an "access".
///
/// \tparam Key The type of the keys being monitored.
template <typename Key>
class Statistics {
public:
using size_t = std::size_t;
using InitializerList = std::initializer_list<Key>;
/// Constructor.
Statistics() noexcept : _total_accesses(0), _total_hits(0) {
}
/// Constructor.
///
/// \param keys Any number of keys to monitor.
template <typename... Keys,
typename = std::enable_if_t<Internal::all_of_type<Key, Keys...>>>
explicit Statistics(Keys&&... keys) : Statistics() {
// clang-format off
Internal::for_each([this](auto&& key) {
this->monitor(std::forward<decltype(key)>(key));
}, std::forward<Keys>(keys)...);
// clang-format on
}
/// Constructor.
///
/// \param range A range of keys to monitor.
template <typename Range, typename = Internal::enable_if_range<Range>>
explicit Statistics(const Range& range)
: Statistics(std::begin(range), std::end(range)) {
}
/// Constructor.
///
/// \param begin The start iterator of a range of keys to monitor.
/// \param end The end iterator of a range of keys to monitor.
template <typename Iterator,
typename = Internal::enable_if_iterator<Iterator>>
Statistics(Iterator begin, Iterator end) : Statistics() {
for (; begin != end; ++begin) {
monitor(*begin);
}
}
/// Constructor.
///
/// \param list A list of keys to monitor.
Statistics(InitializerList list) // NOLINT(runtime/explicit)
: Statistics(list.begin(), list.end()) {
}
/// \returns The total number of accesses (hits + misses) made to the cache.
size_t total_accesses() const noexcept {
return _total_accesses;
}
/// \returns The total number of hits made to the cache.
size_t total_hits() const noexcept {
return _total_hits;
}
/// \returns The total number of misses made to the cache.
size_t total_misses() const noexcept {
return total_accesses() - total_hits();
}
/// \returns The ratio of hits ($\in [0, 1]$) relative to all accesses.
double hit_rate() const noexcept {
return static_cast<double>(total_hits()) / total_accesses();
}
/// \returns The ratio of misses ($\in [0, 1]$) relative to all accesses.
double miss_rate() const noexcept {
return 1 - hit_rate();
}
/// \returns The number of hits for the given key.
/// \param key The key to retrieve the hits for.
/// \throws LRU::UnmonitoredKey if the key was not registered for monitoring.
size_t hits_for(const Key& key) const {
return stats_for(key).hits;
}
/// \returns The number of misses for the given key.
/// \param key The key to retrieve the misses for.
/// \throws LRU::UnmonitoredKey if the key was not registered for monitoring.
size_t misses_for(const Key& key) const {
return stats_for(key).misses;
}
/// \returns The number of accesses (hits + misses) for the given key.
/// \param key The key to retrieve the accesses for.
/// \throws LRU::UnmonitoredKey if the key was not registered for monitoring.
size_t accesses_for(const Key& key) const {
return stats_for(key).accesses();
}
/// \returns A `KeyStatistics` object for the given key.
/// \param key The key to retrieve the stats for.
/// \throws LRU::UnmonitoredKey if the key was not registered for monitoring.
const KeyStatistics& stats_for(const Key& key) const {
auto iterator = _key_map.find(key);
if (iterator == _key_map.end()) {
throw LRU::Error::UnmonitoredKey();
}
return iterator->second;
}
/// \copydoc stats_for()
const KeyStatistics& operator[](const Key& key) const {
return stats_for(key);
}
/// Registers the key for monitoring.
///
/// If the key was already registered, this is a no-op (most importantly, the
/// old statistics are __not__ wiped).
///
/// \param key The key to register.
void monitor(const Key& key) {
// emplace does nothing if the key is already present
_key_map.emplace(key, KeyStatistics());
}
/// Unregisters the given key from monitoring.
///
/// \param key The key to unregister.
/// \throws LRU::Error::UnmonitoredKey if the key was never registered for
/// monitoring.
void unmonitor(const Key& key) {
auto iterator = _key_map.find(key);
if (iterator == _key_map.end()) {
throw LRU::Error::UnmonitoredKey();
} else {
_key_map.erase(iterator);
}
}
/// Unregisters all keys from monitoring.
void unmonitor_all() {
_key_map.clear();
}
/// Clears all statistics for the given key, but keeps on monitoring it.
///
/// \param key The key to reset.
void reset_key(const Key& key) {
auto iterator = _key_map.find(key);
if (iterator == _key_map.end()) {
throw LRU::Error::UnmonitoredKey();
} else {
iterator->second.reset();
}
}
/// Clears the statistics of all keys, but keeps on monitoring it them.
void reset_all() {
for (auto& pair : _key_map) {
_key_map.second.reset();
}
}
/// \returns True if the given key is currently registered for monitoring,
/// else false.
/// \param key The key to check for.
bool is_monitoring(const Key& key) const noexcept {
return _key_map.count(key);
}
/// \returns The number of keys currnetly being monitored.
size_t number_of_monitored_keys() const noexcept {
return _key_map.size();
}
/// \returns True if currently any keys at all are being monitored, else
/// false.
bool is_monitoring_keys() const noexcept {
return !_key_map.empty();
}
private:
template <typename>
friend class Internal::StatisticsMutator;
using HitMap = std::unordered_map<Key, KeyStatistics>;
/// The total number of accesses made for any key.
size_t _total_accesses;
/// The total number of htis made for any key.
size_t _total_hits;
/// The map to keep track of statistics for monitored keys.
HitMap _key_map;
};
namespace Lowercase {
template <typename... Ts>
using statistics = Statistics<Ts...>;
} // namespace Lowercase
} // namespace LRU
#endif // LRU_STATISTICS_HPP

View File

@@ -0,0 +1,391 @@
/// 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 LRU_TIMED_CACHE_HPP
#define LRU_TIMED_CACHE_HPP
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <functional>
#include <iterator>
#include <list>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include <lru/error.hpp>
#include <lru/internal/base-cache.hpp>
#include <lru/internal/last-accessed.hpp>
#include <lru/internal/timed-information.hpp>
namespace LRU {
namespace Internal {
template <typename Key,
typename Value,
typename HashFunction,
typename KeyEqual>
using TimedCacheBase = BaseCache<Key,
Value,
Internal::TimedInformation,
HashFunction,
KeyEqual,
Tag::TimedCache>;
} // namespace Internal
/// A timed LRU cache.
///
/// A timed LRU cache behaves like a regular LRU cache, but adds the concept of
/// "expiration". The cache now not only remembers the order of insertion, but
/// also the point in time at which each element was inserted into the cache.
/// The cache then has an additional "time to live" property, which designates
/// the time after which a key in the cache is said to be "expired". Once a key
/// has expired, the cache will behave as if the key were not present in the
/// cache at all and, for example, return false on calls to `contains()` or
/// throw on calls to `lookup()`.
///
/// \see LRU::Cache
template <typename Key,
typename Value,
typename Duration = std::chrono::duration<double, std::milli>,
typename HashFunction = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>>
class TimedCache
: public Internal::TimedCacheBase<Key, Value, HashFunction, KeyEqual> {
private:
using super = Internal::TimedCacheBase<Key, Value, HashFunction, KeyEqual>;
using PRIVATE_BASE_CACHE_MEMBERS;
public:
using Tag = LRU::Tag::TimedCache;
using PUBLIC_BASE_CACHE_MEMBERS;
using super::ordered_end;
using super::unordered_end;
using typename super::size_t;
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(size_t,const HashFunction&,const KeyEqual&)
template <typename AnyDurationType = Duration>
explicit TimedCache(const AnyDurationType& time_to_live,
size_t capacity = Internal::DEFAULT_CAPACITY,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, hash, equal)
, _time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(size_t,Iterator,Iterator,const
/// HashFunction&,const
/// KeyEqual&)
template <typename Iterator, typename AnyDurationType = Duration>
TimedCache(const AnyDurationType& time_to_live,
size_t capacity,
Iterator begin,
Iterator end,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, begin, end, hash, equal)
, _time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(Iterator,Iterator,const HashFunction&,const
/// KeyEqual&)
template <typename Iterator, typename AnyDurationType = Duration>
TimedCache(const AnyDurationType& time_to_live,
Iterator begin,
Iterator end,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(begin, end, hash, equal)
, _time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(Range,size_t,const HashFunction&,const
/// KeyEqual&)
template <typename Range,
typename AnyDurationType = Duration,
typename = Internal::enable_if_range<Range>>
TimedCache(const AnyDurationType& time_to_live,
size_t capacity,
Range&& range,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(capacity, std::forward<Range>(range), hash, equal)
, _time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(Range,const HashFunction&,const
/// KeyEqual&)
template <typename Range,
typename AnyDurationType = Duration,
typename = Internal::enable_if_range<Range>>
explicit TimedCache(const AnyDurationType& time_to_live,
Range&& range,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual())
: super(std::forward<Range>(range), hash, equal)
, _time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(InitializerList,const HashFunction&,const
/// KeyEqual&)
template <typename AnyDurationType = Duration>
TimedCache(const AnyDurationType& time_to_live,
InitializerList list,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual()) // NOLINT(runtime/explicit)
: super(list, hash, equal),
_time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \param time_to_live The time to live for keys in the cache.
/// \copydoc BaseCache::BaseCache(InitializerList,size_t,const
/// HashFunction&,const
/// KeyEqual&)
template <typename AnyDurationType = Duration>
TimedCache(const AnyDurationType& time_to_live,
size_t capacity,
InitializerList list,
const HashFunction& hash = HashFunction(),
const KeyEqual& equal = KeyEqual()) // NOLINT(runtime/explicit)
: super(capacity, list, hash, equal),
_time_to_live(std::chrono::duration_cast<Duration>(time_to_live)) {
}
/// \copydoc BaseCache::swap
void swap(TimedCache& other) noexcept {
using std::swap;
super::swap(other);
swap(_time_to_live, other._time_to_live);
}
/// Swaps the contents of one cache with another cache.
///
/// \param first The first cache to swap.
/// \param second The second cache to swap.
friend void swap(TimedCache& first, TimedCache& second) noexcept {
first.swap(second);
}
/// \copydoc BaseCache::find(const Key&)
UnorderedIterator find(const Key& key) override {
auto iterator = _map.find(key);
if (iterator != _map.end()) {
if (!_has_expired(iterator->second)) {
_register_hit(key, iterator->second.value);
_move_to_front(iterator->second.order);
_last_accessed = iterator;
return {*this, iterator};
}
}
_register_miss(key);
return end();
}
/// \copydoc BaseCache::find(const Key&) const
UnorderedConstIterator find(const Key& key) const override {
auto iterator = _map.find(key);
if (iterator != _map.end()) {
if (!_has_expired(iterator->second)) {
_register_hit(key, iterator->second.value);
_move_to_front(iterator->second.order);
_last_accessed = iterator;
return {*this, iterator};
}
}
_register_miss(key);
return cend();
}
// no front() because we may have to erase the
// entire cache if everything happens to be expired
/// \returns True if all keys in the cache have expired, else false.
bool all_expired() const {
// By the laws of predicate logic, any statement about any empty set is true
if (is_empty()) return true;
/// If the most-recently inserted key has expired, all others must have too.
auto latest = _map.find(_order.back());
return _has_expired(latest->second);
}
/// Erases all expired elements from the cache.
///
/// \complexity O(N)
/// \returns The number of elements erased.
size_t clear_expired() {
// We have to do a linear search here because linked lists do not
// support O(log N) binary searches given their node-based nature.
// Either way, in the worst case the entire cache has expired and
// we would have to do O(N) erasures.
if (is_empty()) return 0;
auto iterator = _order.begin();
size_t number_of_erasures = 0;
while (iterator != _order.end()) {
auto map_iterator = _map.find(*iterator);
// If the current element hasn't expired, also all elements inserted
// after will not have, so we can stop.
if (!_has_expired(map_iterator->second)) break;
_erase(map_iterator);
iterator = _order.begin();
number_of_erasures += 1;
}
return number_of_erasures;
}
/// \returns True if the given key is contained in the cache and has expired.
/// \param key The key to test expiration for.
bool has_expired(const Key& key) const noexcept {
auto iterator = _map.find(key);
return iterator != _map.end() && _has_expired(iterator->second);
}
/// \returns True if the key pointed to by the iterator has expired.
/// \param ordered_iterator The ordered iterator to check.
/// \details If this is the end iterator, this method returns false.
bool has_expired(OrderedConstIterator ordered_iterator) const noexcept {
if (ordered_iterator == ordered_end()) return false;
auto iterator = _map.find(ordered_iterator->key());
assert(iterator != _map.end());
return _has_expired(iterator->second);
}
/// \returns True if the key pointed to by the iterator has expired.
/// \param unordered_iterator The unordered iterator to check.
/// \details If this is the end iterator, this method returns false.
bool has_expired(UnorderedConstIterator unordered_iterator) const noexcept {
if (unordered_iterator == unordered_end()) return false;
assert(unordered_iterator._iterator != _map.end());
return _has_expired(unordered_iterator._iterator->second);
}
/// \copydoc BaseCache::is_valid(UnorderedConstIterator)
bool is_valid(UnorderedConstIterator unordered_iterator) const
noexcept override {
if (!super::is_valid(unordered_iterator)) return false;
if (has_expired(unordered_iterator)) return false;
return true;
}
/// \copydoc BaseCache::is_valid(OrderedConstIterator)
bool is_valid(OrderedConstIterator ordered_iterator) const noexcept override {
if (!super::is_valid(ordered_iterator)) return false;
if (has_expired(ordered_iterator)) return false;
return true;
}
/// \copydoc BaseCache::is_valid(UnorderedConstIterator)
/// \throws LRU::Error::KeyExpired if the key pointed to by the iterator has
/// expired.
void
throw_if_invalid(UnorderedConstIterator unordered_iterator) const override {
super::throw_if_invalid(unordered_iterator);
if (has_expired(unordered_iterator)) {
throw LRU::Error::KeyExpired();
}
}
/// \copydoc BaseCache::is_valid(OrderedConstIterator)
/// \throws LRU::Error::KeyExpired if the key pointed to by the iterator has
/// expired.
void throw_if_invalid(OrderedConstIterator ordered_iterator) const override {
super::throw_if_invalid(ordered_iterator);
if (has_expired(ordered_iterator)) {
throw LRU::Error::KeyExpired();
}
}
private:
using Clock = Internal::Clock;
/// \returns True if the last accessed object is valid.
/// \details Next to performing the base cache's action, this method also
/// checks for expiration of the last accessed key.
bool _last_accessed_is_ok(const Key& key) const noexcept override {
if (!super::_last_accessed_is_ok(key)) return false;
return !_has_expired(_last_accessed.information());
}
/// \copydoc _value_for_last_accessed() const
Value& _value_for_last_accessed() override {
auto& information = _last_accessed.information();
if (_has_expired(information)) {
throw LRU::Error::KeyExpired();
} else {
return information.value;
}
}
/// Attempts to access the last accessed key's value.
/// \throws LRU::Error::KeyExpired if the key has expired.
/// \returns The value of the last accessed key.
const Value& _value_for_last_accessed() const override {
const auto& information = _last_accessed.information();
if (_has_expired(information)) {
throw LRU::Error::KeyExpired();
} else {
return information.value;
}
}
/// Checks if a key has expired, given its information.
///
/// \param information The information to check expiration with.
/// \returns True if the key has expired, else false.
bool _has_expired(const Information& information) const noexcept {
auto elapsed = Clock::now() - information.insertion_time;
return std::chrono::duration_cast<Duration>(elapsed) > _time_to_live;
}
/// The duration after which a key is said to be expired.
Duration _time_to_live;
};
namespace Lowercase {
template <typename... Ts>
using timed_cache = TimedCache<Ts...>;
} // namespace Lowercase
} // namespace LRU
#endif // LRU_TIMED_CACHE_HPP

View File

@@ -0,0 +1,99 @@
/// 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 LRU_WRAP_HPP
#define LRU_WRAP_HPP
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include <lru/cache.hpp>
#include <lru/internal/hash.hpp>
#include <lru/internal/utility.hpp>
namespace LRU {
/// Wraps a function with a "shallow" LRU cache.
///
/// Given a function, this function will return a new function, where
/// "top-level" calls are cached. With "top-level" or "shallow", we mean
/// that recursive calls to the same function are not cached, since those
/// will call the original function symbol, not the wrapped one.
///
/// \tparam CacheType The cache template class to use.
/// \param original_function The function to wrap.
/// \param args Any arguments to forward to the cache.
/// \returns A new function with a shallow LRU cache.
template <typename Function,
template <typename...> class CacheType = Cache,
typename... Args>
auto wrap(Function original_function, Args&&... args) {
return [
original_function,
cache_args = std::forward_as_tuple(std::forward<Args>(args)...)
](auto&&... arguments) mutable {
using Arguments = std::tuple<std::decay_t<decltype(arguments)>...>;
using ReturnType = decltype(
original_function(std::forward<decltype(arguments)>(arguments)...));
static_assert(!std::is_void<ReturnType>::value,
"Return type of wrapped function must not be void");
static auto cache =
Internal::construct_from_tuple<CacheType<Arguments, ReturnType>>(
cache_args);
auto key = std::make_tuple(arguments...);
auto iterator = cache.find(key);
if (iterator != cache.end()) {
return iterator->second;
}
auto value =
original_function(std::forward<decltype(arguments)>(arguments)...);
cache.emplace(key, value);
return value;
};
}
/// Wraps a function with a "shallow" LRU timed cache.
///
/// Given a function, this function will return a new function, where
/// "top-level" calls are cached. With "top-level" or "shallow", we mean
/// that recursive calls to the same function are not cached, since those
/// will call the original function symbol, not the wrapped one.
///
/// \param original_function The function to wrap.
/// \param args Any arguments to forward to the cache.
/// \returns A new function with a shallow LRU cache.
template <typename Function, typename Duration, typename... Args>
auto timed_wrap(Function original_function, Duration duration, Args&&... args) {
return wrap<Function, TimedCache>(
original_function, duration, std::forward<Args>(args)...);
}
} // namespace LRU
#endif // LRU_WRAP_HPP

View File

@@ -0,0 +1,56 @@
########################################
# CONFIG
########################################
add_compile_options(-g -Werror -DDEBUG)
########################################
# DEPENDENCIES
########################################
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest)
set(GTEST_INCLUDE_DIRS
${gtest_SOURCE_DIR}/include
${gtest_SOURCE_DIR})
########################################
# INCLUDES
########################################
include_directories(${GTEST_INCLUDE_DIRS})
########################################
# SOURCES
########################################
set(TEST_LRU_CACHE_SOURCES
move-awareness-test.cpp
last-accessed-test.cpp
iterator-test.cpp
cache-test.cpp
timed-cache-test.cpp
statistics-test.cpp
wrap-test.cpp
callback-test.cpp
)
###########################################################
## BINARIES
###########################################################
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
########################################
# TARGET
########################################
add_executable(lru-cache-test ${TEST_LRU_CACHE_SOURCES})
target_link_libraries(lru-cache-test gtest gtest_main)
add_test(
NAME lru-cache-test
COMMAND lru-cache-test
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
)

View File

@@ -0,0 +1,545 @@
/// 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.
#include <algorithm>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
using namespace LRU;
struct CacheTest : public ::testing::Test {
using CacheType = Cache<std::string, int>;
template <typename Cache, typename Range>
bool is_equal_to_range(const Cache& cache, const Range& range) {
using std::begin;
return std::equal(cache.ordered_begin(), cache.ordered_end(), begin(range));
}
CacheType cache;
};
TEST(CacheConstructionTest, IsConstructibleFromInitializerList) {
Cache<std::string, int> cache = {
{"one", 1}, {"two", 2}, {"three", 3},
};
EXPECT_FALSE(cache.is_empty());
EXPECT_EQ(cache.size(), 3);
EXPECT_EQ(cache["one"], 1);
EXPECT_EQ(cache["two"], 2);
EXPECT_EQ(cache["three"], 3);
}
TEST(CacheConstructionTest, IsConstructibleFromInitializerListWithCapacity) {
// clang-format off
Cache<std::string, int> cache(2, {
{"one", 1}, {"two", 2}, {"three", 3},
});
// clang-format on
EXPECT_FALSE(cache.is_empty());
EXPECT_EQ(cache.size(), 2);
EXPECT_FALSE(cache.contains("one"));
EXPECT_EQ(cache["two"], 2);
EXPECT_EQ(cache["three"], 3);
}
TEST(CacheConstructionTest, IsConstructibleFromRange) {
const std::vector<std::pair<std::string, int>> range = {
{"one", 1}, {"two", 2}, {"three", 3}};
Cache<std::string, int> cache(range);
EXPECT_FALSE(cache.is_empty());
EXPECT_EQ(cache.size(), 3);
EXPECT_EQ(cache["one"], 1);
EXPECT_EQ(cache["two"], 2);
EXPECT_EQ(cache["three"], 3);
}
TEST(CacheConstructionTest, IsConstructibleFromIterators) {
std::vector<std::pair<std::string, int>> range = {
{"one", 1}, {"two", 2}, {"three", 3}};
Cache<std::string, int> cache(range.begin(), range.end());
EXPECT_FALSE(cache.is_empty());
EXPECT_EQ(cache.size(), 3);
EXPECT_EQ(cache["one"], 1);
EXPECT_EQ(cache["two"], 2);
EXPECT_EQ(cache["three"], 3);
}
TEST(CacheConstructionTest, CapacityIsMaxOfInternalDefaultAndIteratorDistance) {
std::vector<std::pair<std::string, int>> range = {
{"one", 1}, {"two", 2}, {"three", 3}};
Cache<std::string, int> cache(range.begin(), range.end());
EXPECT_EQ(cache.capacity(), Internal::DEFAULT_CAPACITY);
for (int i = 0; i < Internal::DEFAULT_CAPACITY; ++i) {
range.emplace_back(std::to_string(i), i);
}
cache = std::move(range);
EXPECT_EQ(cache.capacity(), range.size());
Cache<std::string, int> cache2(range.begin(), range.end());
EXPECT_EQ(cache2.capacity(), range.size());
}
TEST(CacheConstructionTest, UsesCustomHashFunction) {
using MockHash = std::function<int(int)>;
std::size_t mock_hash_call_count = 0;
MockHash mock_hash = [&mock_hash_call_count](int value) {
mock_hash_call_count += 1;
return value;
};
Cache<int, int, decltype(mock_hash)> cache(128, mock_hash);
EXPECT_EQ(mock_hash_call_count, 0);
cache.contains(5);
EXPECT_EQ(mock_hash_call_count, 1);
}
TEST(CacheConstructionTest, UsesCustomKeyEqual) {
using MockCompare = std::function<bool(int, int)>;
std::size_t mock_equal_call_count = 0;
MockCompare mock_equal = [&mock_equal_call_count](int a, int b) {
mock_equal_call_count += 1;
return a == b;
};
Cache<int, int, std::hash<int>, decltype(mock_equal)> cache(
128, std::hash<int>(), mock_equal);
EXPECT_EQ(mock_equal_call_count, 0);
cache.insert(5, 1);
ASSERT_TRUE(cache.contains(5));
EXPECT_EQ(mock_equal_call_count, 1);
}
TEST_F(CacheTest, ContainsAfterInsertion) {
ASSERT_TRUE(cache.is_empty());
for (std::size_t i = 1; i <= 100; ++i) {
const auto key = std::to_string(i);
cache.insert(key, i);
EXPECT_EQ(cache.size(), i);
EXPECT_TRUE(cache.contains(key));
}
EXPECT_FALSE(cache.is_empty());
}
TEST_F(CacheTest, ContainsAfteEmplacement) {
ASSERT_TRUE(cache.is_empty());
for (std::size_t i = 1; i <= 100; ++i) {
const auto key = std::to_string(i);
cache.emplace(key, i);
EXPECT_EQ(cache.size(), i);
EXPECT_TRUE(cache.contains(key));
}
EXPECT_FALSE(cache.is_empty());
}
TEST_F(CacheTest, RemovesLRUElementWhenFull) {
cache.capacity(2);
ASSERT_EQ(cache.capacity(), 2);
cache.emplace("one", 1);
cache.emplace("two", 2);
ASSERT_EQ(cache.size(), 2);
ASSERT_TRUE(cache.contains("one"));
ASSERT_TRUE(cache.contains("two"));
cache.emplace("three", 3);
EXPECT_EQ(cache.size(), 2);
EXPECT_TRUE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
EXPECT_FALSE(cache.contains("one"));
}
TEST_F(CacheTest, LookupReturnsTheRightValue) {
for (std::size_t i = 1; i <= 10; ++i) {
const auto key = std::to_string(i);
cache.emplace(key, i);
ASSERT_EQ(cache.size(), i);
EXPECT_EQ(cache.lookup(key), i);
EXPECT_EQ(cache[key], i);
}
}
TEST_F(CacheTest, LookupOnlyThrowsWhenKeyNotFound) {
cache.emplace("one", 1);
ASSERT_EQ(cache.size(), 1);
EXPECT_EQ(cache.lookup("one"), 1);
EXPECT_THROW(cache.lookup("two"), LRU::Error::KeyNotFound);
EXPECT_THROW(cache.lookup("three"), LRU::Error::KeyNotFound);
cache.emplace("two", 2);
EXPECT_EQ(cache.lookup("two"), 2);
}
TEST_F(CacheTest, SizeIsUpdatedProperly) {
ASSERT_EQ(cache.size(), 0);
for (std::size_t i = 1; i <= 10; ++i) {
cache.emplace(std::to_string(i), i);
// Use ASSERT and not EXPECT to terminate the loop early
ASSERT_EQ(cache.size(), i);
}
for (std::size_t i = 10; i >= 1; --i) {
ASSERT_EQ(cache.size(), i);
cache.erase(std::to_string(i));
// Use ASSERT and not EXPECT to terminate the loop early
}
EXPECT_EQ(cache.size(), 0);
}
TEST_F(CacheTest, SpaceLeftWorks) {
cache.capacity(10);
ASSERT_EQ(cache.size(), 0);
for (std::size_t i = 10; i >= 1; --i) {
EXPECT_EQ(cache.space_left(), i);
cache.emplace(std::to_string(i), i);
}
EXPECT_EQ(cache.space_left(), 0);
}
TEST_F(CacheTest, IsEmptyWorks) {
ASSERT_TRUE(cache.is_empty());
cache.emplace("one", 1);
EXPECT_FALSE(cache.is_empty());
cache.clear();
EXPECT_TRUE(cache.is_empty());
}
TEST_F(CacheTest, IsFullWorks) {
ASSERT_FALSE(cache.is_full());
cache.capacity(0);
ASSERT_TRUE(cache.is_full());
cache.capacity(2);
cache.emplace("one", 1);
EXPECT_FALSE(cache.is_full());
cache.emplace("two", 1);
EXPECT_TRUE(cache.is_full());
cache.clear();
EXPECT_FALSE(cache.is_full());
}
TEST_F(CacheTest, CapacityCanBeAdjusted) {
cache.capacity(10);
ASSERT_EQ(cache.capacity(), 10);
for (std::size_t i = 0; i < 10; ++i) {
cache.emplace(std::to_string(i), i);
}
ASSERT_EQ(cache.size(), 10);
cache.emplace("foo", 0xdeadbeef);
EXPECT_EQ(cache.size(), 10);
cache.capacity(11);
ASSERT_EQ(cache.capacity(), 11);
cache.emplace("bar", 0xdeadbeef);
EXPECT_EQ(cache.size(), 11);
cache.capacity(5);
EXPECT_EQ(cache.capacity(), 5);
EXPECT_EQ(cache.size(), 5);
cache.capacity(0);
EXPECT_EQ(cache.capacity(), 0);
EXPECT_EQ(cache.size(), 0);
cache.capacity(128);
EXPECT_EQ(cache.capacity(), 128);
EXPECT_EQ(cache.size(), 0);
}
TEST_F(CacheTest, EraseErasesAndReturnsTrueWhenElementContained) {
cache.emplace("one", 1);
ASSERT_TRUE(cache.contains("one"));
EXPECT_TRUE(cache.erase("one"));
EXPECT_FALSE(cache.contains("one"));
}
TEST_F(CacheTest, EraseReturnsFalseWhenElementNotContained) {
ASSERT_FALSE(cache.contains("one"));
EXPECT_FALSE(cache.erase("one"));
}
TEST_F(CacheTest, ClearRemovesAllElements) {
ASSERT_TRUE(cache.is_empty());
cache.emplace("one", 1);
EXPECT_FALSE(cache.is_empty());
cache.clear();
EXPECT_TRUE(cache.is_empty());
}
TEST_F(CacheTest, ShrinkAdjustsSizeWell) {
cache.emplace("one", 1);
cache.emplace("two", 2);
ASSERT_EQ(cache.size(), 2);
cache.shrink(1);
EXPECT_EQ(cache.size(), 1);
cache.emplace("three", 2);
cache.emplace("four", 3);
ASSERT_EQ(cache.size(), 3);
cache.shrink(1);
EXPECT_EQ(cache.size(), 1);
cache.shrink(0);
EXPECT_TRUE(cache.is_empty());
}
TEST_F(CacheTest, ShrinkDoesNothingWhenRequestedSizeIsGreaterThanCurrent) {
cache.emplace("one", 1);
cache.emplace("two", 2);
ASSERT_EQ(cache.size(), 2);
cache.shrink(50);
EXPECT_EQ(cache.size(), 2);
}
TEST_F(CacheTest, ShrinkRemovesLRUElements) {
cache.emplace("one", 1);
cache.emplace("two", 2);
cache.emplace("three", 3);
ASSERT_EQ(cache.size(), 3);
cache.shrink(2);
EXPECT_EQ(cache.size(), 2);
EXPECT_FALSE(cache.contains("one"));
EXPECT_TRUE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
cache.shrink(1);
EXPECT_EQ(cache.size(), 1);
EXPECT_FALSE(cache.contains("one"));
EXPECT_FALSE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
}
TEST_F(CacheTest, CanInsertIterators) {
using Range = std::vector<std::pair<std::string, int>>;
Range range = {{"one", 1}, {"two", 2}, {"three", 3}};
EXPECT_EQ(cache.insert(range.begin(), range.end()), 3);
EXPECT_TRUE(is_equal_to_range(cache, range));
Range range2 = {{"one", 1}, {"four", 4}};
EXPECT_EQ(cache.insert(range2.begin(), range2.end()), 1);
// clang-format off
EXPECT_TRUE(is_equal_to_range(cache, Range({
{"two", 2}, {"three", 3}, {"one", 1}, {"four", 4}
})));
// clang-format on
}
TEST_F(CacheTest, CanInsertRange) {
std::vector<std::pair<std::string, int>> range = {
{"one", 1}, {"two", 2}, {"three", 3}};
cache.insert(range);
EXPECT_TRUE(is_equal_to_range(cache, range));
}
TEST_F(CacheTest, CanInsertList) {
std::initializer_list<std::pair<std::string, int>> list = {
{"one", 1}, {"two", 2}, {"three", 3}};
// Do it like this, just to verify that template deduction fails if only
// the range function exists and no explicit overload for the initializer list
cache.insert({{"one", 1}, {"two", 2}, {"three", 3}});
EXPECT_TRUE(is_equal_to_range(cache, list));
}
TEST_F(CacheTest, ResultIsCorrectForInsert) {
auto result = cache.insert("one", 1);
EXPECT_TRUE(result.was_inserted());
EXPECT_TRUE(result);
EXPECT_EQ(result.iterator(), cache.begin());
result = cache.insert("one", 1);
EXPECT_FALSE(result.was_inserted());
EXPECT_FALSE(result);
EXPECT_EQ(result.iterator(), cache.begin());
}
TEST_F(CacheTest, ResultIsCorrectForEmplace) {
auto result = cache.emplace("one", 1);
EXPECT_TRUE(result.was_inserted());
EXPECT_TRUE(result);
EXPECT_EQ(result.iterator(), cache.begin());
result = cache.emplace("one", 1);
EXPECT_FALSE(result.was_inserted());
EXPECT_FALSE(result);
EXPECT_EQ(result.iterator(), cache.begin());
}
TEST_F(CacheTest, CapacityIsSameAfterCopy) {
cache.capacity(100);
auto cache2 = cache;
EXPECT_EQ(cache.capacity(), cache2.capacity());
}
TEST_F(CacheTest, CapacityIsSameAfterMove) {
cache.capacity(100);
auto cache2 = std::move(cache);
EXPECT_EQ(cache2.capacity(), 100);
}
TEST_F(CacheTest, ComparisonOperatorWorks) {
ASSERT_EQ(cache, cache);
auto cache2 = cache;
EXPECT_EQ(cache, cache2);
cache.emplace("one", 1);
cache2.emplace("one", 1);
EXPECT_EQ(cache, cache2);
cache.emplace("two", 2);
cache2.emplace("two", 2);
EXPECT_EQ(cache, cache2);
cache.erase("two");
EXPECT_NE(cache, cache2);
}
TEST_F(CacheTest, SwapWorks) {
auto cache2 = cache;
cache.emplace("one", 1);
cache2.emplace("two", 2);
ASSERT_TRUE(cache.contains("one"));
ASSERT_TRUE(cache2.contains("two"));
cache.swap(cache2);
EXPECT_FALSE(cache.contains("one"));
EXPECT_TRUE(cache.contains("two"));
EXPECT_FALSE(cache2.contains("two"));
EXPECT_TRUE(cache2.contains("one"));
}
TEST_F(CacheTest, SizeStaysZeroWhenCapacityZero) {
cache.capacity(0);
ASSERT_EQ(cache.capacity(), 0);
ASSERT_EQ(cache.size(), 0);
auto result = cache.insert("one", 1);
EXPECT_EQ(cache.capacity(), 0);
EXPECT_EQ(cache.size(), 0);
EXPECT_FALSE(result.was_inserted());
EXPECT_EQ(result.iterator(), cache.end());
result = cache.emplace("two", 2);
EXPECT_EQ(cache.capacity(), 0);
EXPECT_EQ(cache.size(), 0);
EXPECT_FALSE(result.was_inserted());
EXPECT_EQ(result.iterator(), cache.end());
}
TEST_F(CacheTest, LookupsMoveElementsToFront) {
cache.capacity(2);
cache.insert({{"one", 1}, {"two", 2}});
// The LRU principle mandates that lookups place
// accessed elements to the front.
typename CacheType::OrderedIterator iterator(cache.find("one"));
cache.emplace("three", 3);
EXPECT_TRUE(cache.contains("one"));
EXPECT_FALSE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
EXPECT_EQ(std::prev(cache.ordered_end()).key(), "three");
EXPECT_EQ(cache.front(), "three");
EXPECT_EQ(cache.back(), "one");
ASSERT_EQ(cache.lookup("one"), 1);
EXPECT_EQ(std::prev(cache.ordered_end()).key(), "one");
EXPECT_EQ(cache.ordered_begin().key(), "three");
EXPECT_EQ(cache.front(), "one");
EXPECT_EQ(cache.back(), "three");
}

View File

@@ -0,0 +1,155 @@
/// 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.
#include <array>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
using namespace LRU;
struct CallbackTest : public ::testing::Test {
Cache<int, int> cache;
};
TEST_F(CallbackTest, HitCallbacksGetCalled) {
std::array<int, 3> counts = {0, 0, 0};
cache.hit_callback([&counts](auto& key, auto& value) { counts[key] += 1; });
cache.emplace(0, 0);
cache.emplace(1, 1);
cache.emplace(2, 2);
ASSERT_TRUE(cache.contains(0));
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], 0);
cache.find(2);
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], 1);
cache.lookup(1);
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], 1);
EXPECT_EQ(counts[2], 1);
cache.lookup(0);
EXPECT_EQ(counts[0], 2);
EXPECT_EQ(counts[1], 1);
EXPECT_EQ(counts[2], 1);
cache.contains(5);
EXPECT_EQ(counts[0], 2);
EXPECT_EQ(counts[1], 1);
EXPECT_EQ(counts[2], 1);
}
TEST_F(CallbackTest, MissCallbacksGetCalled) {
std::array<int, 3> counts = {0, 0, 0};
cache.miss_callback([&counts](auto& key) { counts[key] += 1; });
cache.emplace(0, 0);
ASSERT_TRUE(cache.contains(0));
EXPECT_EQ(counts[0], 0);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], 0);
cache.find(2);
EXPECT_EQ(counts[0], 0);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], 1);
cache.find(1);
EXPECT_EQ(counts[0], 0);
EXPECT_EQ(counts[1], 1);
EXPECT_EQ(counts[2], 1);
cache.contains(1);
EXPECT_EQ(counts[0], 0);
EXPECT_EQ(counts[1], 2);
EXPECT_EQ(counts[2], 1);
}
TEST_F(CallbackTest, AccessCallbacksGetCalled) {
std::array<int, 3> counts = {0, 0, 0};
cache.access_callback(
[&counts](auto& key, bool found) { counts[key] += found ? 1 : -1; });
cache.emplace(0, 0);
ASSERT_TRUE(cache.contains(0));
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], 0);
cache.find(2);
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], 0);
EXPECT_EQ(counts[2], -1);
cache.find(1);
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], -1);
EXPECT_EQ(counts[2], -1);
cache.contains(1);
EXPECT_EQ(counts[0], 1);
EXPECT_EQ(counts[1], -2);
EXPECT_EQ(counts[2], -1);
cache.find(0);
EXPECT_EQ(counts[0], 2);
EXPECT_EQ(counts[1], -2);
EXPECT_EQ(counts[2], -1);
}
TEST_F(CallbackTest, CallbacksAreNotCalledAfterBeingCleared) {
int hit = 0, miss = 0, access = 0;
cache.hit_callback([&hit](auto&, auto&) { hit += 1; });
cache.miss_callback([&miss](auto&) { miss += 1; });
cache.access_callback([&access](auto&, bool) { access += 1; });
cache.emplace(0, 0);
cache.contains(0);
cache.find(1);
ASSERT_EQ(hit, 1);
ASSERT_EQ(miss, 1);
ASSERT_EQ(access, 2);
cache.clear_all_callbacks();
cache.contains(0);
cache.find(1);
cache.find(2);
ASSERT_EQ(hit, 1);
ASSERT_EQ(miss, 1);
ASSERT_EQ(access, 2);
}

View File

@@ -0,0 +1,312 @@
/// 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.
#include <chrono>
#include <string>
#include <thread>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
using namespace LRU;
using namespace std::chrono_literals;
struct IteratorTest : public ::testing::Test {
using CacheType = Cache<std::string, int>;
using UnorderedIterator = typename CacheType::UnorderedIterator;
using UnorderedConstIterator = typename CacheType::UnorderedConstIterator;
using OrderedIterator = typename CacheType::OrderedIterator;
using OrderedConstIterator = typename CacheType::OrderedConstIterator;
CacheType cache;
};
TEST_F(IteratorTest, UnorderedIteratorsAreCompatibleAsExpected) {
cache.emplace("one", 1);
// Move construction
UnorderedIterator first(cache.unordered_begin());
// Copy construction
UnorderedIterator second(first);
// Copy assignment
UnorderedIterator third;
third = second;
// Move construction from non-const to const
UnorderedConstIterator first_const(std::move(first));
// Copy construction from non-const to const
UnorderedConstIterator second_const(second);
// Copy assignment
UnorderedConstIterator third_const;
third_const = third;
}
TEST_F(IteratorTest, OrderedIteratorsAreCompatibleAsExpected) {
cache.emplace("one", 1);
// Move construction
OrderedIterator first(cache.ordered_begin());
// Copy construction
OrderedIterator second(first);
// Copy assignment
OrderedIterator third;
third = second;
// Move construction from non-const to const
OrderedConstIterator first_const(std::move(first));
// Copy construction from non-const to const
OrderedConstIterator second_const(second);
// Copy assignment
OrderedConstIterator third_const;
third_const = third;
}
TEST_F(IteratorTest, OrderedAndUnorderedAreComparable) {
cache.emplace("one", 1);
// Basic assumptions
ASSERT_EQ(cache.unordered_begin(), cache.unordered_begin());
ASSERT_EQ(cache.ordered_begin(), cache.ordered_begin());
ASSERT_EQ(cache.unordered_end(), cache.unordered_end());
ASSERT_EQ(cache.ordered_end(), cache.ordered_end());
EXPECT_EQ(cache.unordered_begin(), cache.ordered_begin());
// We need to ensure symmetry!
EXPECT_EQ(cache.ordered_begin(), cache.unordered_begin());
// This is an exceptional property we expect
EXPECT_EQ(cache.unordered_end(), cache.ordered_end());
EXPECT_EQ(cache.ordered_end(), cache.unordered_end());
// These assumptions should hold because there is only one element
// so the unordered iterator will convert to an ordered iterator, then
// compare equal because both point to the same single element.
EXPECT_EQ(cache.ordered_begin(), cache.unordered_begin());
EXPECT_EQ(cache.unordered_begin(), cache.ordered_begin());
cache.emplace("two", 1);
// But then the usual assumptions should hold
EXPECT_NE(cache.ordered_begin(), cache.find("two"));
EXPECT_NE(cache.find("two"), cache.ordered_begin());
}
TEST_F(IteratorTest, TestConversionFromUnorderedToOrdered) {
cache.emplace("one", 1);
cache.emplace("two", 2);
cache.emplace("three", 3);
// Note: find() will always return end() - 1
UnorderedIterator unordered = cache.find("one");
ASSERT_EQ(unordered.key(), "one");
ASSERT_EQ(unordered.value(), 1);
OrderedIterator ordered(unordered);
ordered = OrderedIterator(unordered);
EXPECT_EQ(ordered.key(), "one");
EXPECT_EQ(ordered.value(), 1);
// Once it's ordered, the ordering shold be maintained
--ordered;
EXPECT_EQ(ordered.key(), "three");
EXPECT_EQ(ordered.value(), 3);
UnorderedConstIterator const_unordered = unordered;
const_unordered = unordered;
OrderedConstIterator const_ordered(std::move(const_unordered));
const_ordered = OrderedConstIterator(std::move(const_unordered));
// Just making sure this compiles
const_ordered = --ordered;
const_ordered = OrderedConstIterator(unordered);
EXPECT_EQ(ordered.key(), "two");
EXPECT_EQ(ordered.value(), 2);
}
TEST_F(IteratorTest, OrdereredIteratorsAreOrdered) {
for (std::size_t i = 0; i < 100; ++i) {
cache.emplace(std::to_string(i), i);
}
auto iterator = cache.ordered_begin();
for (std::size_t i = 0; i < 100; ++i, ++iterator) {
ASSERT_EQ(iterator.value(), i);
}
}
TEST_F(IteratorTest, OrderedIteratorsDoNotChangeTheOrderOfElements) {
cache.capacity(2);
cache.insert({{"one", 1}});
auto begin = cache.ordered_begin();
cache.emplace("two", 2);
// This here will cause a lookup, but it should not
// change the order of elements
ASSERT_EQ(begin->key(), "one");
ASSERT_EQ((++begin)->key(), "two");
ASSERT_EQ((--begin)->key(), "one");
cache.emplace("three", 3);
EXPECT_FALSE(cache.contains("one"));
EXPECT_TRUE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
}
TEST_F(IteratorTest, UnorderedIteratorsDoNotChangeTheOrderOfElements) {
cache.capacity(2);
cache.insert({{"one", 1}});
auto begin = cache.unordered_begin();
cache.emplace("two", 2);
ASSERT_EQ(begin->key(), "one");
cache.emplace("three", 3);
EXPECT_FALSE(cache.contains("one"));
EXPECT_TRUE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
ASSERT_EQ(cache.back(), "two");
ASSERT_EQ(cache.front(), "three");
}
TEST_F(IteratorTest, OrderedIteratorsThrowWhenAccessingExpiredElements) {
TimedCache<int, int> timed_cache(0ms);
timed_cache.emplace(1, 1);
auto iterator = timed_cache.ordered_begin();
EXPECT_THROW(iterator.entry(), LRU::Error::KeyExpired);
}
TEST_F(IteratorTest, UnorderedIteratorsThrowWhenAccessingExpiredElements) {
TimedCache<int, int> timed_cache(0ms);
timed_cache.emplace(1, 1);
auto iterator = timed_cache.unordered_begin();
EXPECT_THROW(iterator.entry(), LRU::Error::KeyExpired);
}
TEST_F(IteratorTest, IsValidReturnsTrueForValidIterators) {
cache.emplace("one", 1);
cache.emplace("two", 1);
auto ordered_iterator = cache.ordered_begin();
EXPECT_TRUE(cache.is_valid(ordered_iterator));
EXPECT_TRUE(cache.is_valid(++ordered_iterator));
auto unordered_iterator = cache.unordered_begin();
EXPECT_TRUE(cache.is_valid(unordered_iterator));
EXPECT_TRUE(cache.is_valid(++unordered_iterator));
}
TEST_F(IteratorTest, IsValidReturnsFalseForInvalidIterators) {
TimedCache<int, int> timed_cache(0ms);
EXPECT_FALSE(cache.is_valid(cache.ordered_begin()));
EXPECT_FALSE(cache.is_valid(cache.ordered_end()));
EXPECT_FALSE(cache.is_valid(cache.unordered_begin()));
EXPECT_FALSE(cache.is_valid(cache.unordered_end()));
timed_cache.emplace(1, 1);
EXPECT_FALSE(cache.is_valid(cache.ordered_begin()));
EXPECT_FALSE(cache.is_valid(cache.unordered_begin()));
}
TEST_F(IteratorTest, ThrowIfInvalidThrowsAsExpected) {
EXPECT_THROW(cache.throw_if_invalid(cache.ordered_begin()),
LRU::Error::InvalidIterator);
EXPECT_THROW(cache.throw_if_invalid(cache.ordered_end()),
LRU::Error::InvalidIterator);
EXPECT_THROW(cache.throw_if_invalid(cache.unordered_begin()),
LRU::Error::InvalidIterator);
EXPECT_THROW(cache.throw_if_invalid(cache.unordered_end()),
LRU::Error::InvalidIterator);
TimedCache<int, int> timed_cache(0s, {{1, 1}});
ASSERT_EQ(timed_cache.size(), 1);
EXPECT_THROW(timed_cache.throw_if_invalid(timed_cache.ordered_begin()),
LRU::Error::KeyExpired);
EXPECT_THROW(timed_cache.throw_if_invalid(timed_cache.unordered_begin()),
LRU::Error::KeyExpired);
}
TEST_F(IteratorTest, DereferencingNeverThrows) {
TimedCache<int, int> timed_cache(1ms, {{1, 1}});
// Test valid iterators.
EXPECT_EQ(timed_cache.ordered_begin()->key(), 1);
EXPECT_EQ(timed_cache.unordered_begin()->key(), 1);
std::this_thread::sleep_for(1ms);
// Test invalid iterators.
*timed_cache.ordered_begin();
*timed_cache.unordered_begin();
timed_cache.ordered_begin()->key();
timed_cache.unordered_begin()->key();
timed_cache.ordered_begin()->value();
timed_cache.unordered_begin()->value();
}
TEST_F(IteratorTest, CallingAccessThrowsForInvalidIterators) {
TimedCache<int, int> timed_cache(1ms, {{1, 1}});
// Test valid iterators.
ASSERT_EQ(timed_cache.ordered_begin()->key(), 1);
ASSERT_EQ(timed_cache.unordered_begin()->key(), 1);
std::this_thread::sleep_for(1ms);
// Test invalid iterators.
EXPECT_THROW(timed_cache.ordered_begin().key(), LRU::Error::KeyExpired);
EXPECT_THROW(timed_cache.unordered_begin().key(), LRU::Error::KeyExpired);
EXPECT_THROW(timed_cache.ordered_begin().value(), LRU::Error::KeyExpired);
EXPECT_THROW(timed_cache.unordered_begin().value(), LRU::Error::KeyExpired);
EXPECT_THROW(timed_cache.ordered_end().key(), LRU::Error::InvalidIterator);
EXPECT_THROW(timed_cache.unordered_end().key(), LRU::Error::InvalidIterator);
EXPECT_THROW(timed_cache.ordered_end().value(), LRU::Error::InvalidIterator);
EXPECT_THROW(timed_cache.unordered_end().value(),
LRU::Error::InvalidIterator);
}

View File

@@ -0,0 +1,109 @@
/// 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.
#include <iterator>
#include <string>
#include <unordered_map>
#include "gtest/gtest.h"
#include "lru/internal/last-accessed.hpp"
using namespace LRU::Internal;
struct LastAccessedTest : public ::testing::Test {
using Map = std::unordered_map<std::string, int>;
static Map map;
};
// clang-format off
LastAccessedTest::Map LastAccessedTest::map = {
{"one", 1},
{"two", 2},
{"three", 3}
};
// clang-format on
TEST_F(LastAccessedTest, IsAssignableFromConstAndNonConst) {
auto front = map.find("one");
LastAccessed<std::string, int> last_accessed(front->first, front->second);
ASSERT_EQ(last_accessed.key(), "one");
ASSERT_EQ(last_accessed.information(), 1);
last_accessed = map.find("two");
EXPECT_EQ(last_accessed.key(), "two");
EXPECT_EQ(last_accessed.information(), 2);
last_accessed = map.find("three");
EXPECT_EQ(last_accessed.key(), "three");
EXPECT_EQ(last_accessed.information(), 3);
}
TEST_F(LastAccessedTest, IsComparableWithConstAndNonConstIterators) {
auto front = map.find("one");
LastAccessed<std::string, int> last_accessed(front->first, front->second);
// non-const
EXPECT_EQ(last_accessed, front);
EXPECT_EQ(front, last_accessed);
EXPECT_NE(map.find("two"), last_accessed);
EXPECT_NE(last_accessed, map.find("three"));
// const
Map::const_iterator const_front = map.find("one");
EXPECT_EQ(last_accessed, const_front);
EXPECT_EQ(const_front, last_accessed);
Map::const_iterator iterator = map.find("two");
EXPECT_NE(iterator, last_accessed);
iterator = map.find("three");
EXPECT_NE(last_accessed, iterator);
}
TEST_F(LastAccessedTest, IsComparableToConstAndNonConstKeys) {
using namespace std::string_literals;
std::string key = "forty-two";
int information = 42;
LastAccessed<std::string, int> last_accessed(key, information);
EXPECT_EQ(last_accessed, key);
EXPECT_EQ(key, last_accessed);
EXPECT_EQ(last_accessed, "forty-two"s);
EXPECT_EQ("forty-two"s, last_accessed);
const std::string& key_const_reference = key;
EXPECT_EQ(key_const_reference, last_accessed);
EXPECT_EQ(last_accessed, key_const_reference);
EXPECT_NE(last_accessed, "asdf"s);
EXPECT_NE(last_accessed, "foo"s);
EXPECT_NE(last_accessed, "forty-three"s);
}

166
src/3rdParty/lru-cache/tests/logbt.sh vendored Normal file
View File

@@ -0,0 +1,166 @@
#!/bin/bash
# Taken from:
# https://github.com/mapbox/logbt
set -eu
set -o pipefail
shopt -s nullglob
export CORE_DIRECTORY=/tmp/logbt-coredumps
function error() {
>&2 echo "$@"
exit 1
}
if [[ $(uname -s) == 'Linux' ]]; then
if ! which gdb > /dev/null; then
error "Could not find required command 'gdb'"
fi
# if we have sudo then set core pattern
if [[ $(id -u) == 0 ]]; then
echo "Setting $(cat /proc/sys/kernel/core_pattern) -> ${CORE_DIRECTORY}/core.%p.%E"
echo "${CORE_DIRECTORY}/core.%p.%E" > /proc/sys/kernel/core_pattern
else
# if we cannot modify the pattern we assert it has
# already been set as we expect and need
if [[ $(cat /proc/sys/kernel/core_pattern) != '/tmp/logbt-coredumps/core.%p.%E' ]]; then
error "unexpected core_pattern: $(cat /proc/sys/kernel/core_pattern)"
exit 1
fi
echo "Using existing corefile location: $(cat /proc/sys/kernel/core_pattern)"
fi
else
if ! which lldb > /dev/null; then
error "Could not find required command 'lldb'"
exit 1
fi
# if we have sudo then set core pattern
if [[ $(id -u) == 0 ]]; then
sudo sysctl kern.corefile=${CORE_DIRECTORY}/core.%P
else
if [[ $(sysctl -n kern.corefile) == '/cores/core.%P' ]]; then
# OS X default is /cores/core.%P which works for logbt out of the box
export CORE_DIRECTORY=/cores
elif [[ $(sysctl -n kern.corefile) == '${CORE_DIRECTORY}/core.%P' ]]; then
# all good, previously set
:
else
# restore default with:
# sudo sysctl kern.corefile=/cores/core.%P
error "unexpected core_pattern: $(sysctl -n kern.corefile)"
exit 1
fi
echo "Using existing corefile location: $(sysctl -n kern.corefile)"
fi
# Recommend running with the following setting to only show crashes
# in the notification center
# defaults write com.apple.CrashReporter UseUNC 1
fi
if [[ ! -d ${CORE_DIRECTORY} ]]; then
# TODO: enable this once tests are adapted to extra stdout
# echo "Creating directory for core files at '${CORE_DIRECTORY}'"
mkdir -p ${CORE_DIRECTORY}
fi
# ensure we can write to the directory, otherwise
# core files might not be able to be written
WRITE_RETURN=0
touch ${CORE_DIRECTORY}/test.txt || WRITE_RETURN=$?
if [[ ${WRITE_RETURN} != 0 ]]; then
error "Permissions problem: unable to write to ${CORE_DIRECTORY} (exited with ${WRITE_RETURN})"
exit 1
else
# cleanup from test
rm ${CORE_DIRECTORY}/test.txt
fi
function process_core() {
if [[ $(uname -s) == 'Darwin' ]]; then
lldb --core ${2} --batch -o 'thread backtrace all' -o 'quit'
else
gdb ${1} --core ${2} -ex "set pagination 0" -ex "thread apply all bt" --batch
fi
# note: on OS X the -f avoids a hang on prompt 'remove write-protected regular file?'
rm -f ${2}
}
function backtrace {
local code=$?
echo "$1 exited with code:${code}"
if [[ $(uname -s) == 'Darwin' ]]; then
local COREFILE="${CORE_DIRECTORY}/core.${CHILD_PID}"
if [ -e ${COREFILE} ]; then
echo "Found core at ${COREFILE}"
process_core $1 ${COREFILE}
else
if [[ ${code} != 0 ]]; then
echo "No core found at ${COREFILE}"
fi
fi
else
local SEARCH_PATTERN_BY_PID="core.${CHILD_PID}.*"
local hit=false
for corefile in ${CORE_DIRECTORY}/${SEARCH_PATTERN_BY_PID}; do
echo "Found core at ${corefile}"
# extract program name from corefile
filename=$(basename "${corefile}")
binary_program=/$(echo ${filename##*.\!} | tr '!' '/')
process_core ${binary_program} ${corefile}
hit=true
done
if [[ ${hit} == false ]] && [[ ${code} != 0 ]]; then
echo "No core found at ${CORE_DIRECTORY}/${SEARCH_PATTERN_BY_PID}"
fi
fi
local SEARCH_PATTERN_NON_TRACKED="core.*"
local hit=false
for corefile in ${CORE_DIRECTORY}/${SEARCH_PATTERN_NON_TRACKED}; do
echo "Found non-tracked core at ${corefile}"
hit=true
done
if [[ ${code} != 0 ]]; then
if [[ ${hit} == true ]]; then
echo "Processing cores..."
fi
for corefile in ${CORE_DIRECTORY}/${SEARCH_PATTERN_NON_TRACKED}; do
filename=$(basename "${corefile}")
binary_program=/$(echo ${filename##*.\!} | tr '!' '/')
process_core ${binary_program} ${corefile}
done
else
if [[ ${hit} == true ]]; then
echo "Skipping processing cores..."
fi
fi
exit $code
}
function warn_on_existing_cores() {
local SEARCH_PATTERN_NON_TRACKED="core.*"
# at startup warn about existing corefiles, since these are unexpected
for corefile in ${CORE_DIRECTORY}/${SEARCH_PATTERN_NON_TRACKED}; do
echo "WARNING: Found existing corefile at ${corefile}"
done
}
warn_on_existing_cores
# Hook up function to run when logbt exits
trap "backtrace $1" EXIT
# Enable corefile generation
ulimit -c unlimited
# Run the child process in a background process
# in order to get the PID
$* & export CHILD_PID=$!
# Keep logbt running as long as the child is running
# to be able to hook into a potential crash
wait ${CHILD_PID}

View File

@@ -0,0 +1,171 @@
/// 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.
#include <cstddef>
#include <string>
#include <utility>
template <typename SubClassTemplateParameterJustForNewStaticMembersHehehe>
struct MoveAwareBase {
static std::size_t move_count;
static std::size_t non_move_count;
static std::size_t forwarding_count;
static std::size_t copy_count;
static void reset() {
move_count = 0;
non_move_count = 0;
forwarding_count = 0;
copy_count = 0;
}
MoveAwareBase(const MoveAwareBase& other) : s(other.s) {
copy_count += 1;
}
MoveAwareBase(MoveAwareBase&& other) : s(std::move(other.s)) {
// Just need to implement so it's not deactivated
// (because we do need the copy constructor)
}
MoveAwareBase(std::string&& s_) : s(std::move(s_)) {
move_count += 1;
}
MoveAwareBase(std::string& s_) : s(s_) {
non_move_count += 1;
}
MoveAwareBase(const char* s_) : s(s_) {
forwarding_count += 1;
}
MoveAwareBase(const int& x, const double& y)
: s(std::to_string(x) + std::to_string(y)) {
non_move_count += 1;
}
MoveAwareBase(int&& x, double&& y)
: s(std::to_string(x) + std::to_string(y)) {
move_count += 1;
}
virtual ~MoveAwareBase() = default;
MoveAwareBase& operator=(const MoveAwareBase& other) {
copy_count += 1;
s = other.s;
return *this;
}
MoveAwareBase& operator=(MoveAwareBase&& other) {
s = std::move(other.s);
return *this;
}
bool operator==(const MoveAwareBase& other) const noexcept {
return this->s == other.s;
}
bool operator!=(const MoveAwareBase& other) const noexcept {
return !(*this == other);
}
std::string s;
};
template <typename T>
std::size_t MoveAwareBase<T>::move_count = 0;
template <typename T>
std::size_t MoveAwareBase<T>::non_move_count = 0;
template <typename T>
std::size_t MoveAwareBase<T>::forwarding_count = 0;
template <typename T>
std::size_t MoveAwareBase<T>::copy_count = 0;
struct MoveAwareKey : public MoveAwareBase<MoveAwareKey> {
using super = MoveAwareBase<MoveAwareKey>;
// clang-format off
MoveAwareKey() = default;
MoveAwareKey(const MoveAwareKey& other) : super(other) {}
MoveAwareKey(MoveAwareKey&& other) : super(std::move(other)) {}
MoveAwareKey(std::string&& s_) : super(std::move(s_)) {}
MoveAwareKey(std::string& s_) : super(s_) {}
MoveAwareKey(const char* s_) : super(s_) {}
MoveAwareKey(const int& x, const double& y) : super(x, y) {}
MoveAwareKey(int&& x, double&& y) : super(std::move(x), std::move(y)) {}
// clang-format on
MoveAwareKey& operator=(const MoveAwareKey& other) {
super::operator=(other);
return *this;
}
MoveAwareKey& operator=(MoveAwareKey&& other) {
super::operator=(std::move(other));
return *this;
}
};
struct MoveAwareValue : public MoveAwareBase<MoveAwareValue> {
using super = MoveAwareBase<MoveAwareValue>;
// clang-format off
MoveAwareValue() = default;
MoveAwareValue(const MoveAwareValue& other) : super(other) {}
MoveAwareValue(MoveAwareValue&& other) : super(std::move(other)) {}
MoveAwareValue(std::string&& s_) : super(std::move(s_)) {}
MoveAwareValue(std::string& s_) : super(s_) {}
MoveAwareValue(const char* s_) : super(s_) {}
MoveAwareValue(const int& x, const double& y) : super(x, y) {}
MoveAwareValue(int&& x, double&& y) : super(std::move(x), std::move(y)) {}
// clang-format on
MoveAwareValue& operator=(const MoveAwareValue& other) {
super::operator=(other);
return *this;
}
MoveAwareValue& operator=(MoveAwareValue&& other) {
super::operator=(std::move(other));
return *this;
}
};
namespace std {
template <>
struct hash<MoveAwareKey> {
auto operator()(const MoveAwareKey& key) const {
return hash<std::string>()(key.s);
}
};
template <>
struct hash<MoveAwareValue> {
auto operator()(const MoveAwareValue& value) const {
return hash<std::string>()(value.s);
}
};
} // namespace std

View File

@@ -0,0 +1,154 @@
/// 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.
#include <string>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
#include "tests/move-aware-dummies.hpp"
struct MoveAwarenessTest : public ::testing::Test {
MoveAwarenessTest() {
MoveAwareKey::reset();
MoveAwareValue::reset();
}
LRU::Cache<MoveAwareKey, MoveAwareValue> cache;
};
TEST_F(MoveAwarenessTest, DoesNotMoveForInsert) {
cache.insert("x", "y");
// One construction (right there)
ASSERT_EQ(MoveAwareKey::forwarding_count, 1);
ASSERT_EQ(MoveAwareValue::forwarding_count, 1);
ASSERT_EQ(MoveAwareKey::copy_count, 1);
// Values only go into the map
ASSERT_EQ(MoveAwareValue::copy_count, 1);
// Do this at the end to avoid incrementing the counts
ASSERT_EQ(cache["x"], "y");
}
TEST_F(MoveAwarenessTest, ForwardsValuesWell) {
cache.emplace("x", "y");
// One construction to make the key first
EXPECT_GE(MoveAwareKey::forwarding_count, 1);
EXPECT_GE(MoveAwareValue::forwarding_count, 1);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
ASSERT_EQ(cache["x"], "y");
}
TEST_F(MoveAwarenessTest, MovesSingleRValues) {
cache.emplace(std::string("x"), std::string("y"));
// Move constructions from the string
EXPECT_EQ(MoveAwareKey::move_count, 1);
EXPECT_EQ(MoveAwareValue::move_count, 1);
EXPECT_EQ(MoveAwareKey::non_move_count, 0);
EXPECT_EQ(MoveAwareValue::non_move_count, 0);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
ASSERT_EQ(cache["x"], "y");
}
TEST_F(MoveAwarenessTest, CopiesSingleLValues) {
std::string x("x");
std::string y("y");
cache.emplace(x, y);
// Move constructions from the string
EXPECT_EQ(MoveAwareKey::non_move_count, 1);
EXPECT_EQ(MoveAwareValue::non_move_count, 1);
EXPECT_EQ(MoveAwareKey::move_count, 0);
EXPECT_EQ(MoveAwareValue::move_count, 0);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
ASSERT_EQ(cache["x"], "y");
}
TEST_F(MoveAwarenessTest, MovesRValueTuples) {
cache.emplace(std::piecewise_construct,
std::forward_as_tuple(1, 3.14),
std::forward_as_tuple(2, 2.718));
// construct_from_tuple performs one move construction
// (i.e. construction from rvalues)
EXPECT_EQ(MoveAwareKey::move_count, 1);
EXPECT_EQ(MoveAwareValue::move_count, 1);
EXPECT_EQ(MoveAwareKey::non_move_count, 0);
EXPECT_EQ(MoveAwareValue::non_move_count, 0);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
}
TEST_F(MoveAwarenessTest, MovesLValueTuples) {
int x = 1, z = 2;
double y = 3.14, w = 2.718;
cache.emplace(std::piecewise_construct,
std::forward_as_tuple(x, y),
std::forward_as_tuple(z, w));
// construct_from_tuple will perfom one copy construction
// (i.e. construction from lvalues)
EXPECT_EQ(MoveAwareKey::non_move_count, 1);
EXPECT_EQ(MoveAwareValue::non_move_count, 1);
EXPECT_EQ(MoveAwareKey::move_count, 0);
EXPECT_EQ(MoveAwareValue::move_count, 0);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
}
TEST_F(MoveAwarenessTest, MovesElementsOutOfRValueRanges) {
std::vector<std::pair<std::string, std::string>> range = {{"x", "y"}};
cache.insert(std::move(range));
// Move constructions from the string
EXPECT_EQ(MoveAwareKey::move_count, 1);
EXPECT_EQ(MoveAwareValue::move_count, 1);
EXPECT_EQ(MoveAwareKey::non_move_count, 0);
EXPECT_EQ(MoveAwareValue::non_move_count, 0);
EXPECT_EQ(MoveAwareKey::copy_count, 0);
EXPECT_EQ(MoveAwareValue::copy_count, 0);
ASSERT_EQ(cache["x"], "y");
}

View File

@@ -0,0 +1,375 @@
/// 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.
#include <memory>
#include <vector>
#include "gtest/gtest.h"
#include "lru/internal/statistics-mutator.hpp"
#include "lru/lru.hpp"
using namespace LRU;
using namespace LRU::Internal;
TEST(StatisticsTest, ConstructsWellFromRange) {
std::vector<int> range = {1, 2, 3};
Statistics<int> stats(range);
for (const auto& i : range) {
ASSERT_TRUE(stats.is_monitoring(i));
}
}
TEST(StatisticsTest, ConstructsWellFromIterator) {
std::vector<int> range = {1, 2, 3};
Statistics<int> stats(range.begin(), range.end());
for (const auto& i : range) {
ASSERT_TRUE(stats.is_monitoring(i));
}
}
TEST(StatisticsTest, ConstructsWellFromInitializerList) {
Statistics<int> stats({1, 2, 3});
std::vector<int> range = {1, 2, 3};
for (const auto& i : range) {
ASSERT_TRUE(stats.is_monitoring(i));
}
}
TEST(StatisticsTest, ConstructsWellFromVariadicArguments) {
Statistics<int> stats(1, 2, 3);
std::vector<int> range = {1, 2, 3};
for (const auto& i : range) {
ASSERT_TRUE(stats.is_monitoring(i));
}
}
TEST(StatisticsTest, EmptyPreconditions) {
Statistics<int> stats;
EXPECT_FALSE(stats.is_monitoring_keys());
EXPECT_EQ(stats.number_of_monitored_keys(), 0);
EXPECT_FALSE(stats.is_monitoring(1));
EXPECT_FALSE(stats.is_monitoring(2));
EXPECT_EQ(stats.total_accesses(), 0);
EXPECT_EQ(stats.total_hits(), 0);
EXPECT_EQ(stats.total_misses(), 0);
}
TEST(StatisticsTest, StatisticsMutatorCanRegisterHits) {
auto stats = std::make_shared<Statistics<int>>(1, 2, 3);
StatisticsMutator<int> mutator(stats);
mutator.register_hit(1);
EXPECT_EQ(stats->hits_for(1), 1);
EXPECT_EQ(stats->total_accesses(), 1);
EXPECT_EQ(stats->total_hits(), 1);
EXPECT_EQ(stats->total_misses(), 0);
EXPECT_EQ(stats->hit_rate(), 1);
EXPECT_EQ(stats->miss_rate(), 0);
mutator.register_hit(1);
EXPECT_EQ(stats->hits_for(1), 2);
EXPECT_EQ(stats->total_accesses(), 2);
EXPECT_EQ(stats->total_hits(), 2);
EXPECT_EQ(stats->total_misses(), 0);
EXPECT_EQ(stats->hit_rate(), 1);
EXPECT_EQ(stats->miss_rate(), 0);
mutator.register_hit(2);
EXPECT_EQ(stats->hits_for(1), 2);
EXPECT_EQ(stats->hits_for(2), 1);
EXPECT_EQ(stats->total_accesses(), 3);
EXPECT_EQ(stats->total_hits(), 3);
EXPECT_EQ(stats->total_misses(), 0);
EXPECT_EQ(stats->hit_rate(), 1);
EXPECT_EQ(stats->miss_rate(), 0);
}
TEST(StatisticsTest, StatisticsMutatorCanRegisterMisses) {
auto stats = std::make_shared<Statistics<int>>(1, 2, 3);
StatisticsMutator<int> mutator(stats);
mutator.register_miss(1);
EXPECT_EQ(stats->misses_for(1), 1);
EXPECT_EQ(stats->total_accesses(), 1);
EXPECT_EQ(stats->total_hits(), 0);
EXPECT_EQ(stats->total_misses(), 1);
EXPECT_EQ(stats->hit_rate(), 0);
EXPECT_EQ(stats->miss_rate(), 1);
mutator.register_miss(1);
EXPECT_EQ(stats->misses_for(1), 2);
EXPECT_EQ(stats->total_accesses(), 2);
EXPECT_EQ(stats->total_hits(), 0);
EXPECT_EQ(stats->total_misses(), 2);
EXPECT_EQ(stats->hit_rate(), 0);
EXPECT_EQ(stats->miss_rate(), 1);
mutator.register_miss(2);
EXPECT_EQ(stats->misses_for(1), 2);
EXPECT_EQ(stats->misses_for(2), 1);
EXPECT_EQ(stats->total_accesses(), 3);
EXPECT_EQ(stats->total_hits(), 0);
EXPECT_EQ(stats->total_misses(), 3);
EXPECT_EQ(stats->hit_rate(), 0);
EXPECT_EQ(stats->miss_rate(), 1);
}
TEST(StatisticsTest, CanDynamicallyMonitorAndUnmonitorKeys) {
Statistics<int> stats;
ASSERT_EQ(stats.number_of_monitored_keys(), 0);
stats.monitor(1);
EXPECT_EQ(stats.number_of_monitored_keys(), 1);
EXPECT_TRUE(stats.is_monitoring(1));
EXPECT_FALSE(stats.is_monitoring(2));
stats.monitor(2);
EXPECT_EQ(stats.number_of_monitored_keys(), 2);
EXPECT_TRUE(stats.is_monitoring(1));
EXPECT_TRUE(stats.is_monitoring(2));
stats.unmonitor(1);
EXPECT_EQ(stats.number_of_monitored_keys(), 1);
EXPECT_FALSE(stats.is_monitoring(1));
EXPECT_TRUE(stats.is_monitoring(2));
stats.unmonitor_all();
EXPECT_FALSE(stats.is_monitoring_keys());
EXPECT_FALSE(stats.is_monitoring(1));
EXPECT_FALSE(stats.is_monitoring(2));
}
TEST(StatisticsTest, ThrowsForUnmonitoredKey) {
Statistics<int> stats;
EXPECT_THROW(stats.stats_for(1), LRU::Error::UnmonitoredKey);
EXPECT_THROW(stats.hits_for(2), LRU::Error::UnmonitoredKey);
EXPECT_THROW(stats.misses_for(3), LRU::Error::UnmonitoredKey);
EXPECT_THROW(stats[4], LRU::Error::UnmonitoredKey);
}
TEST(StatisticsTest, RatesAreCalculatedCorrectly) {
auto stats = std::make_shared<Statistics<int>>(1, 2, 3);
StatisticsMutator<int> mutator(stats);
for (std::size_t i = 0; i < 20; ++i) {
mutator.register_hit(1);
}
for (std::size_t i = 0; i < 80; ++i) {
mutator.register_miss(1);
}
EXPECT_EQ(stats->hit_rate(), 0.2);
EXPECT_EQ(stats->miss_rate(), 0.8);
}
TEST(StatisticsTest, CanShareStatistics) {
auto stats = std::make_shared<Statistics<int>>(1, 2, 3);
StatisticsMutator<int> mutator1(stats);
StatisticsMutator<int> mutator2(stats);
StatisticsMutator<int> mutator3(stats);
ASSERT_EQ(mutator1.shared(), mutator2.shared());
ASSERT_EQ(mutator2.shared(), mutator3.shared());
ASSERT_EQ(&mutator2.get(), &mutator3.get());
mutator1.register_hit(1);
EXPECT_EQ(stats->total_accesses(), 1);
EXPECT_EQ(stats->total_hits(), 1);
EXPECT_EQ(stats->total_misses(), 0);
EXPECT_EQ(stats->hits_for(1), 1);
mutator2.register_hit(1);
EXPECT_EQ(stats->total_accesses(), 2);
EXPECT_EQ(stats->total_hits(), 2);
EXPECT_EQ(stats->total_misses(), 0);
EXPECT_EQ(stats->hits_for(1), 2);
mutator3.register_miss(2);
EXPECT_EQ(stats->total_accesses(), 3);
EXPECT_EQ(stats->total_hits(), 2);
EXPECT_EQ(stats->total_misses(), 1);
EXPECT_EQ(stats->hits_for(1), 2);
EXPECT_EQ(stats->misses_for(1), 0);
EXPECT_EQ(stats->hits_for(2), 0);
EXPECT_EQ(stats->misses_for(2), 1);
}
struct CacheWithStatisticsTest : public ::testing::Test {
void assert_total_stats(int accesses, int hits, int misses) {
ASSERT_EQ(cache.stats().total_accesses(), accesses);
ASSERT_EQ(cache.stats().total_hits(), hits);
ASSERT_EQ(cache.stats().total_misses(), misses);
}
void expect_total_stats(int accesses, int hits, int misses) {
EXPECT_EQ(cache.stats().total_accesses(), accesses);
EXPECT_EQ(cache.stats().total_hits(), hits);
EXPECT_EQ(cache.stats().total_misses(), misses);
}
Cache<int, int> cache;
};
TEST_F(CacheWithStatisticsTest,
RequestForCacheStatisticsThrowsWhenNoneRegistered) {
EXPECT_THROW(cache.stats(), LRU::Error::NotMonitoring);
}
TEST_F(CacheWithStatisticsTest, CanRegisterLValueStatistics) {
auto stats = std::make_shared<Statistics<int>>();
cache.monitor(stats);
EXPECT_TRUE(cache.is_monitoring());
// This is a strong constraint, but must hold for lvalue stats object
EXPECT_EQ(&cache.stats(), &*stats);
cache.contains(1);
EXPECT_EQ(cache.shared_stats()->total_accesses(), 1);
EXPECT_EQ(cache.stats().total_misses(), 1);
cache.emplace(1, 2);
cache.contains(1);
EXPECT_EQ(cache.stats().total_accesses(), 2);
EXPECT_EQ(cache.stats().total_misses(), 1);
EXPECT_EQ(cache.stats().total_hits(), 1);
}
TEST_F(CacheWithStatisticsTest, CanRegisterRValueStatistics) {
auto s = std::make_unique<Statistics<int>>(1);
cache.monitor(std::move(s));
EXPECT_TRUE(cache.is_monitoring());
cache.contains(1);
EXPECT_EQ(cache.stats().total_accesses(), 1);
EXPECT_EQ(cache.stats().total_misses(), 1);
cache.emplace(1, 2);
cache.contains(1);
EXPECT_EQ(cache.stats().total_accesses(), 2);
EXPECT_EQ(cache.stats().total_misses(), 1);
EXPECT_EQ(cache.stats().total_hits(), 1);
}
TEST_F(CacheWithStatisticsTest, CanConstructItsOwnStatistics) {
cache.monitor(1, 2, 3);
EXPECT_TRUE(cache.is_monitoring());
EXPECT_TRUE(cache.stats().is_monitoring(1));
EXPECT_TRUE(cache.stats().is_monitoring(2));
EXPECT_TRUE(cache.stats().is_monitoring(3));
cache.contains(1);
EXPECT_EQ(cache.stats().total_accesses(), 1);
EXPECT_EQ(cache.stats().total_misses(), 1);
cache.emplace(1, 2);
cache.contains(1);
EXPECT_EQ(cache.stats().total_accesses(), 2);
EXPECT_EQ(cache.stats().total_misses(), 1);
EXPECT_EQ(cache.stats().total_hits(), 1);
}
TEST_F(CacheWithStatisticsTest, KnowsWhenItIsMonitoring) {
EXPECT_FALSE(cache.is_monitoring());
cache.monitor();
EXPECT_TRUE(cache.is_monitoring());
cache.stop_monitoring();
EXPECT_FALSE(cache.is_monitoring());
}
TEST_F(CacheWithStatisticsTest, StatisticsWorkWithCache) {
cache.monitor(1);
ASSERT_TRUE(cache.is_monitoring());
assert_total_stats(0, 0, 0);
// contains
cache.contains(1);
expect_total_stats(1, 0, 1);
// An access should only occur for lookup(),
// find(), contains() and operator[]
cache.emplace(1, 1);
expect_total_stats(1, 0, 1);
cache.contains(1);
expect_total_stats(2, 1, 1);
// find
cache.find(2);
expect_total_stats(3, 1, 2);
cache.emplace(2, 2);
cache.find(2);
expect_total_stats(4, 2, 2);
EXPECT_THROW(cache.lookup(3), LRU::Error::KeyNotFound);
expect_total_stats(5, 2, 3);
cache.emplace(3, 3);
ASSERT_EQ(cache.lookup(3), 3);
expect_total_stats(6, 3, 3);
EXPECT_THROW(cache[4], LRU::Error::KeyNotFound);
expect_total_stats(7, 3, 4);
cache.emplace(4, 4);
ASSERT_EQ(cache[4], 4);
expect_total_stats(8, 4, 4);
}
TEST_F(CacheWithStatisticsTest, StopsMonitoringWhenAsked) {
auto stats = std::make_shared<Statistics<int>>(1);
cache.monitor(stats);
cache.emplace(1, 1);
ASSERT_TRUE(cache.contains(1));
ASSERT_EQ(cache.stats().hits_for(1), 1);
cache.stop_monitoring();
ASSERT_TRUE(cache.contains(1));
EXPECT_EQ(stats->hits_for(1), 1);
}

View File

@@ -0,0 +1,177 @@
/// 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.
#include <chrono>
#include <initializer_list>
#include <string>
#include <thread>
#include <utility>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
using namespace LRU;
using namespace std::chrono_literals;
TEST(TimedCacheTest, ContainsRespectsExpiration) {
TimedCache<int, int> cache(2ms);
cache.insert(1, 2);
ASSERT_EQ(cache.size(), 1);
ASSERT_TRUE(cache.contains(1));
ASSERT_EQ(cache[1], 2);
std::this_thread::sleep_for(1ms);
EXPECT_TRUE(cache.contains(1));
std::this_thread::sleep_for(1ms);
EXPECT_FALSE(cache.contains(1));
}
TEST(TimedCacheTest, KnowsWhenAllKeysHaveExpired) {
TimedCache<int, int> cache(2ms);
ASSERT_TRUE(cache.all_expired());
cache = {{1, 2}, {2, 3}};
ASSERT_EQ(cache.size(), 2);
ASSERT_FALSE(cache.all_expired());
std::this_thread::sleep_for(1ms);
cache.insert(3, 4);
std::this_thread::sleep_for(1ms);
EXPECT_FALSE(cache.all_expired());
EXPECT_FALSE(cache.contains(1));
EXPECT_FALSE(cache.contains(2));
EXPECT_TRUE(cache.contains(3));
std::this_thread::sleep_for(1ms);
EXPECT_TRUE(cache.all_expired());
EXPECT_FALSE(cache.contains(1));
EXPECT_FALSE(cache.contains(2));
EXPECT_FALSE(cache.contains(3));
}
TEST(TimedCacheTest, CleanExpiredRemovesExpiredElements) {
TimedCache<int, int> cache(2ms, 128, {{1, 2}, {2, 3}});
ASSERT_EQ(cache.size(), 2);
ASSERT_FALSE(cache.all_expired());
std::this_thread::sleep_for(1ms);
cache.insert(3, 4);
ASSERT_EQ(cache.size(), 3);
std::this_thread::sleep_for(1ms);
ASSERT_EQ(cache.size(), 3);
cache.clear_expired();
EXPECT_EQ(cache.size(), 1);
EXPECT_FALSE(cache.contains(1));
EXPECT_FALSE(cache.contains(2));
EXPECT_TRUE(cache.contains(3));
std::this_thread::sleep_for(1ms);
cache.clear_expired();
EXPECT_FALSE(cache.contains(3));
EXPECT_EQ(cache.size(), 0);
EXPECT_TRUE(cache.is_empty());
}
TEST(TimedCacheTest, LookupThrowsWhenKeyExpired) {
TimedCache<int, int> cache(2ms, 128, {{1, 2}});
ASSERT_EQ(cache.lookup(1), 2);
std::this_thread::sleep_for(2ms);
ASSERT_THROW(cache.lookup(1), LRU::Error::KeyExpired);
}
TEST(TimedCacheTest, HasExpiredReturnsFalseForNonContainedKeys) {
TimedCache<int, int> cache(2ms);
EXPECT_FALSE(cache.has_expired(1));
EXPECT_FALSE(cache.has_expired(2));
}
TEST(TimedCacheTest, HasExpiredReturnsFalseForContainedButNotExpiredKeys) {
TimedCache<int, int> cache(100ms);
cache.emplace(1, 1);
cache.emplace(2, 2);
EXPECT_FALSE(cache.has_expired(1));
EXPECT_FALSE(cache.has_expired(2));
}
TEST(TimedCacheTest, HasExpiredReturnsTrueForContainedAndExpiredKeys) {
TimedCache<int, int> cache(2ms);
cache.emplace(1, 1);
std::this_thread::sleep_for(1ms);
cache.emplace(2, 2);
EXPECT_FALSE(cache.has_expired(1));
std::this_thread::sleep_for(1ms);
EXPECT_TRUE(cache.has_expired(1));
EXPECT_FALSE(cache.has_expired(2));
std::this_thread::sleep_for(3ms);
EXPECT_TRUE(cache.has_expired(1));
EXPECT_TRUE(cache.has_expired(2));
}
TEST(TimedCacheTest, LookupsMoveElementsToFront) {
TimedCache<std::string, int> cache(1s);
cache.capacity(2);
cache.insert({{"one", 1}, {"two", 2}});
// The LRU principle mandates that lookups place
// accessed elements to the front. So when we look at
// one it should move to the front.
auto iterator = cache.find("one");
cache.emplace("three", 3);
EXPECT_TRUE(cache.contains("one"));
EXPECT_FALSE(cache.contains("two"));
EXPECT_TRUE(cache.contains("three"));
EXPECT_EQ(++iterator, cache.end());
}

View File

@@ -0,0 +1,90 @@
/// 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.
#include <chrono>
#include "gtest/gtest.h"
#include "lru/lru.hpp"
TEST(WrapTest, CanWrapMutableAndNonMutableLambdas) {
// This is purely to make sure both variants compile
LRU::wrap([](int x) { return x; })(5);
LRU::wrap([](int x) mutable { return x; })(5);
}
TEST(WrapTest, WrappingWorks) {
auto f = [x = 0](int _) mutable {
return ++x;
};
auto wrapped = LRU::wrap(f);
EXPECT_EQ(wrapped(69), 1);
EXPECT_EQ(wrapped(69), 1);
EXPECT_EQ(wrapped(42), 2);
EXPECT_EQ(wrapped(42), 2);
EXPECT_EQ(wrapped(50), 3);
}
TEST(WrapTest, CanPassCapacityArgumentToWrap) {
std::size_t call_count = 0;
auto f = [&call_count](int _) { return call_count += 1; };
auto wrapped1 = LRU::wrap(f, 1);
wrapped1(1);
EXPECT_EQ(call_count, 1);
wrapped1(1);
EXPECT_EQ(call_count, 1);
auto wrapped2 = LRU::wrap(f, 0);
wrapped1(1);
EXPECT_EQ(call_count, 1);
wrapped1(1);
EXPECT_EQ(call_count, 1);
}
TEST(WrapTest, CanPassTimeArgumentToTimedCacheWrap) {
using namespace std::chrono_literals;
std::size_t call_count = 0;
auto f = [&call_count](int _) { return call_count += 1; };
auto wrapped1 = LRU::wrap<decltype(f), LRU::TimedCache>(f, 100ms);
wrapped1(1);
EXPECT_EQ(call_count, 1);
wrapped1(1);
EXPECT_EQ(call_count, 1);
auto wrapped2 = LRU::timed_wrap(f, 0ms);
wrapped1(1);
EXPECT_EQ(call_count, 1);
wrapped1(1);
EXPECT_EQ(call_count, 1);
}