Cachemere
Modular Caching Library for C++
cache.hpp
1 namespace cachemere {
2 
3 template<typename Key,
4  typename Value,
5  template<class, class, class>
6  class InsertionPolicy,
7  template<class, class, class>
8  class EvictionPolicy,
9  template<class, class, class>
10  class ConstraintPolicy,
11  typename MeasureValue,
12  typename MeasureKey,
13  typename KeyHash,
14  bool ThreadSafe>
15 template<typename... Args>
17  : m_insertion_policy(std::make_unique<MyInsertionPolicy>()),
18  m_eviction_policy(std::make_unique<MyEvictionPolicy>()),
19  m_constraint_policy(std::make_unique<MyConstraintPolicy>(std::forward<Args>(args)...)),
20  m_mutex{},
21  m_data{},
22  m_hit_rate_acc(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size),
23  m_byte_hit_rate_acc(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size)
24 {
25 }
26 
27 template<class K,
28  class V,
29  template<class, class, class>
30  class I,
31  template<class, class, class>
32  class E,
33  template<class, class, class>
34  class C,
35  class SV,
36  class SK,
37  class KH,
38  bool TS>
39 template<typename Coll, typename... Args>
40 Cache<K, V, I, E, C, SV, SK, KH, TS>::Cache(Coll& collection, std::tuple<Args...> args)
41  : m_insertion_policy(std::make_unique<MyInsertionPolicy>()),
42  m_eviction_policy(std::make_unique<MyEvictionPolicy>()),
43  m_constraint_policy(std::move(
44  std::apply([](auto&&... params) { return std::make_unique<MyConstraintPolicy>(std::forward<decltype(params)>(params)...); }, std::move(args)))),
45  m_mutex{},
46  m_data{},
47  m_hit_rate_acc(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size),
48  m_byte_hit_rate_acc(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size)
49 {
50  import(collection);
51 }
52 
53 template<class K,
54  class V,
55  template<class, class, class>
56  class I,
57  template<class, class, class>
58  class E,
59  template<class, class, class>
60  class C,
61  class SV,
62  class SK,
63  class KH,
64  bool TS>
65 template<typename KeyView>
66 inline bool Cache<K, V, I, E, C, SV, SK, KH, TS>::contains(const KeyView& key) const
67 {
68  LockGuard guard(lock());
69  return m_data.find(key) != m_data.end();
70 }
71 
72 template<class K,
73  class V,
74  template<class, class, class>
75  class I,
76  template<class, class, class>
77  class E,
78  template<class, class, class>
79  class C,
80  class SV,
81  class SK,
82  class KH,
83  bool TS>
84 template<typename KeyView>
85 std::optional<V> Cache<K, V, I, E, C, SV, SK, KH, TS>::find(const KeyView& key) const
86 {
87  LockGuard guard(lock());
88 
89  auto key_and_item = m_data.find(key);
90  if (key_and_item != m_data.end()) {
91  on_cache_hit(key_and_item->first, key_and_item->second);
92  return key_and_item->second.m_value;
93  }
94 
95  on_cache_miss(key);
96  return std::nullopt;
97 }
98 
99 template<class K,
100  class V,
101  template<class, class, class>
102  class I,
103  template<class, class, class>
104  class E,
105  template<class, class, class>
106  class C,
107  class SV,
108  class SK,
109  class KH,
110  bool TS>
111 template<class Container>
112 void Cache<K, V, I, E, C, SV, SK, KH, TS>::collect_into(Container& container) const
113 {
114  using namespace detail;
115 
116  // Use emplace_back if container is a sequence container, or emplace if container is an associative container.
117  constexpr auto emplace_fn = boost::hana::if_(
118  traits::stl::has_emplace_back<Container, K, V>,
119  [](auto& seq_container, const auto& key, const auto& item) { seq_container.emplace_back(key, item.m_value); },
120  [](auto& assoc_container, const auto& key, const auto& item) { assoc_container.emplace(key, item.m_value); });
121 
122  LockGuard guard(lock());
123 
124  // Reserve space if the container has a reserve() method and a size method().
125  boost::hana::if_(
126  boost::hana::and_(traits::stl::has_reserve<Container>, traits::stl::has_size<Container>),
127  [&](auto& c) { c.reserve(c.size() + m_data.size()); },
128  [](auto&) {})(container);
129 
130  // Copy the cache contents to the container.
131  for (const auto& [key, cached_item] : m_data) {
132  emplace_fn(container, key, cached_item);
133  }
134 }
135 
136 template<class K,
137  class V,
138  template<class, class, class>
139  class I,
140  template<class, class, class>
141  class E,
142  template<class, class, class>
143  class C,
144  class SV,
145  class SK,
146  class KH,
147  bool TS>
149 {
150  LockGuard guard(lock());
151 
152  const auto key_size = static_cast<size_t>(m_measure_key(key));
153  const auto value_size = static_cast<size_t>(m_measure_value(value));
154 
155  CacheItem new_item{key_size, std::move(value), value_size};
156 
157  auto it = m_data.find(key);
158  if (it != m_data.end()) {
159  if (check_replace(key, it->second, new_item)) {
160  // We call insert_or_update because we might have evicted the original key to make room for this one.
161  insert_or_update(std::move(key), std::move(new_item));
162  return true;
163  }
164  } else {
165  if (check_insert(key, new_item)) {
166  const auto it_and_ok = m_data.insert_or_assign(std::move(key), std::move(new_item));
167  assert(it_and_ok.second);
168 
169  on_insert(it_and_ok.first->first, it_and_ok.first->second);
170  return true;
171  }
172  }
173 
174  return false;
175 }
176 
177 template<class K,
178  class V,
179  template<class, class, class>
180  class I,
181  template<class, class, class>
182  class E,
183  template<class, class, class>
184  class C,
185  class SV,
186  class SK,
187  class KH,
188  bool TS>
190 {
191  LockGuard guard(lock());
192 
193  auto key_and_item = m_data.find(key);
194  if (key_and_item != m_data.end()) {
195  remove(key_and_item);
196  return true;
197  }
198  return false;
199 }
200 
201 template<class K,
202  class V,
203  template<class, class, class>
204  class I,
205  template<class, class, class>
206  class E,
207  template<class, class, class>
208  class C,
209  class SV,
210  class SK,
211  class KH,
212  bool TS>
214 {
215  LockGuard guard(lock());
216 
217  m_data.clear();
218 
219  m_hit_rate_acc = MeanAccumulator(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size);
220  m_byte_hit_rate_acc = MeanAccumulator(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size);
221 
222  m_insertion_policy->clear();
223  m_eviction_policy->clear();
224  m_constraint_policy->clear();
225 }
226 
227 template<class K,
228  class V,
229  template<class, class, class>
230  class I,
231  template<class, class, class>
232  class E,
233  template<class, class, class>
234  class C,
235  class SV,
236  class SK,
237  class KH,
238  bool TS>
239 template<class P>
241 {
242  LockGuard guard(lock());
243 
244  for (auto it = m_data.begin(); it != m_data.end();) {
245  const CacheItem& item = it->second;
246 
247  if (!predicate_fn(it->first, item.m_value)) {
248  remove(it++);
249  } else {
250  ++it;
251  }
252  }
253 }
254 
255 template<class K,
256  class V,
257  template<class, class, class>
258  class I,
259  template<class, class, class>
260  class E,
261  template<class, class, class>
262  class C,
263  class SV,
264  class SK,
265  class KH,
266  bool TS>
267 template<class F>
269 {
270  LockGuard guard(lock());
271  for (const auto& [key, value] : m_data) {
272  unary_function(key, value.m_value);
273  }
274 }
275 
276 template<class K,
277  class V,
278  template<class, class, class>
279  class I,
280  template<class, class, class>
281  class E,
282  template<class, class, class>
283  class C,
284  class SV,
285  class SK,
286  class KH,
287  bool TS>
289 {
290  try {
291  // Acquire both cache locks.
292  std::pair<LockGuard, LockGuard> guards{lock_pair(other)};
293 
294  using std::swap;
295 
296  swap(m_statistics_window_size, other.m_statistics_window_size);
297 
298  swap(m_insertion_policy, other.m_insertion_policy);
299  swap(m_eviction_policy, other.m_eviction_policy);
300  swap(m_constraint_policy, other.m_constraint_policy);
301 
302  swap(m_data, other.m_data);
303 
304  swap(m_hit_rate_acc, other.m_hit_rate_acc);
305  swap(m_byte_hit_rate_acc, other.m_byte_hit_rate_acc);
306  } catch (const std::system_error& e) {
307  // The only exception that can sensibly be thrown in the above block is a `system_error` when acquiring the mutexes of both caches (if the caches are
308  // running in thread-safe mode).
309  //
310  // According to the [reference for std mutexes](https://en.cppreference.com/w/cpp/named_req/Mutex), this exception can be thrown for one of two
311  // reasons:
312  // - If the calling thread does not have the required privileges.
313  // - If the implementation determines that acquiring the lock would lead to a deadlock.
314  //
315  // Since both those errors are unrecoverable, we'll validate that the error code is one of the two we expect, and we'll terminate.
316  assert(e.code() == std::errc::operation_not_permitted || e.code() == std::errc::resource_deadlock_would_occur);
317  std::terminate();
318  } catch (...) {
319  // Even though this is technically not possible in the current state of the code, we'll catch any leftover exception to satisfy clang-tidy.
320  std::terminate();
321  }
322 }
323 
324 template<class K,
325  class V,
326  template<class, class, class>
327  class I,
328  template<class, class, class>
329  class E,
330  template<class, class, class>
331  class C,
332  class SV,
333  class SK,
334  class KH,
335  bool TS>
337 {
338  LockGuard guard(lock());
339  return m_data.size();
340 }
341 
342 template<class K,
343  class V,
344  template<class, class, class>
345  class I,
346  template<class, class, class>
347  class E,
348  template<class, class, class>
349  class C,
350  class SV,
351  class SK,
352  class KH,
353  bool TS>
354 template<typename... Args>
356 {
357  LockGuard guard(lock());
358  m_constraint_policy->update(std::forward<Args>(args)...);
359 
360  auto should_evict_another = [&](auto victim_it) { return victim_it != m_eviction_policy->victim_end() && !m_constraint_policy->is_satisfied(); };
361 
362  for (auto victim_it = m_eviction_policy->victim_begin(); should_evict_another(victim_it); victim_it = m_eviction_policy->victim_begin()) {
363  const K& key_to_evict = *victim_it;
364  auto key_and_item = m_data.find(key_to_evict);
365 
366  if (key_and_item != m_data.end()) {
367  remove(key_and_item);
368  } else {
369  // If this trips, the eviction policy tried to evict an item not in cache: the eviction policy and the cache are out of sync.
370  assert(false);
371  }
372  }
373 
374  assert(m_constraint_policy->is_satisfied());
375 }
376 
377 template<class K,
378  class V,
379  template<class, class, class>
380  class I,
381  template<class, class, class>
382  class E,
383  template<class, class, class>
384  class C,
385  class SV,
386  class SK,
387  class KH,
388  bool TS>
390 {
391  return *m_insertion_policy;
392 }
393 
394 template<class K,
395  class V,
396  template<class, class, class>
397  class I,
398  template<class, class, class>
399  class E,
400  template<class, class, class>
401  class C,
402  class SV,
403  class SK,
404  class KH,
405  bool TS>
406 inline auto Cache<K, V, I, E, C, SV, SK, KH, TS>::insertion_policy() const -> const MyInsertionPolicy&
407 {
408  return *m_insertion_policy;
409 }
410 
411 template<class K,
412  class V,
413  template<class, class, class>
414  class I,
415  template<class, class, class>
416  class E,
417  template<class, class, class>
418  class C,
419  class SV,
420  class SK,
421  class KH,
422  bool TS>
424 {
425  return *m_eviction_policy;
426 }
427 
428 template<class K,
429  class V,
430  template<class, class, class>
431  class I,
432  template<class, class, class>
433  class E,
434  template<class, class, class>
435  class C,
436  class SV,
437  class SK,
438  class KH,
439  bool TS>
440 inline auto Cache<K, V, I, E, C, SV, SK, KH, TS>::eviction_policy() const -> const MyEvictionPolicy&
441 {
442  return *m_eviction_policy;
443 }
444 
445 template<class K,
446  class V,
447  template<class, class, class>
448  class I,
449  template<class, class, class>
450  class E,
451  template<class, class, class>
452  class C,
453  class SV,
454  class SK,
455  class KH,
456  bool TS>
458 {
459  return *m_constraint_policy;
460 }
461 
462 template<class K,
463  class V,
464  template<class, class, class>
465  class I,
466  template<class, class, class>
467  class E,
468  template<class, class, class>
469  class C,
470  class SV,
471  class SK,
472  class KH,
473  bool TS>
474 inline auto Cache<K, V, I, E, C, SV, SK, KH, TS>::constraint_policy() const -> const MyConstraintPolicy&
475 {
476  return *m_constraint_policy;
477 }
478 
479 template<class K,
480  class V,
481  template<class, class, class>
482  class I,
483  template<class, class, class>
484  class E,
485  template<class, class, class>
486  class C,
487  class SV,
488  class SK,
489  class KH,
490  bool TS>
492 {
493  return boost::accumulators::rolling_mean(m_hit_rate_acc);
494 }
495 
496 template<class K,
497  class V,
498  template<class, class, class>
499  class I,
500  template<class, class, class>
501  class E,
502  template<class, class, class>
503  class C,
504  class SV,
505  class SK,
506  class KH,
507  bool TS>
509 {
510  return boost::accumulators::rolling_mean(m_byte_hit_rate_acc);
511 }
512 
513 template<class K,
514  class V,
515  template<class, class, class>
516  class I,
517  template<class, class, class>
518  class E,
519  template<class, class, class>
520  class C,
521  class SV,
522  class SK,
523  class KH,
524  bool TS>
526 {
527  return m_statistics_window_size;
528 }
529 
530 template<class K,
531  class V,
532  template<class, class, class>
533  class I,
534  template<class, class, class>
535  class E,
536  template<class, class, class>
537  class C,
538  class SV,
539  class SK,
540  class KH,
541  bool TS>
543 {
544  m_statistics_window_size = window_size;
545 
546  m_hit_rate_acc = MeanAccumulator(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size);
547  m_byte_hit_rate_acc = MeanAccumulator(boost::accumulators::tag::rolling_window::window_size = m_statistics_window_size);
548 }
549 
550 template<class K,
551  class V,
552  template<class, class, class>
553  class I,
554  template<class, class, class>
555  class E,
556  template<class, class, class>
557  class C,
558  class SV,
559  class SK,
560  class KH,
561  bool TS>
562 auto Cache<K, V, I, E, C, SV, SK, KH, TS>::lock() const -> LockGuard
563 {
564  if constexpr (TS) {
565  LockGuard guard{m_mutex};
566  return guard;
567  } else {
568  LockGuard guard;
569  return guard;
570  }
571 }
572 
573 template<class K,
574  class V,
575  template<class, class, class>
576  class I,
577  template<class, class, class>
578  class E,
579  template<class, class, class>
580  class C,
581  class SV,
582  class SK,
583  class KH,
584  bool TS>
585 auto Cache<K, V, I, E, C, SV, SK, KH, TS>::lock([[maybe_unused]] std::defer_lock_t defer_lock_tag) const -> LockGuard
586 {
587  if constexpr (TS) {
588  LockGuard guard{m_mutex, defer_lock_tag};
589  return guard;
590  } else {
591  LockGuard guard;
592  return guard;
593  }
594 }
595 
596 template<class K,
597  class V,
598  template<class, class, class>
599  class I,
600  template<class, class, class>
601  class E,
602  template<class, class, class>
603  class C,
604  class SV,
605  class SK,
606  class KH,
607  bool TS>
608 auto Cache<K, V, I, E, C, SV, SK, KH, TS>::lock_pair(CacheType& other) const -> std::pair<LockGuard, LockGuard>
609 {
610  LockGuard my_guard = lock(std::defer_lock);
611  LockGuard other_guard = other.lock(std::defer_lock);
612 
613  if constexpr (TS) { // std::lock throws if any of the guards don't refer to a mutex.
614  std::lock(my_guard, other_guard);
615  }
616 
617  return std::make_pair<LockGuard, LockGuard>(std::move(my_guard), std::move(other_guard));
618 }
619 
620 template<class K,
621  class V,
622  template<class, class, class>
623  class I,
624  template<class, class, class>
625  class E,
626  template<class, class, class>
627  class C,
628  class SV,
629  class SK,
630  class KH,
631  bool TS>
632 template<class Coll>
633 void Cache<K, V, I, E, C, SV, SK, KH, TS>::import(Coll& collection)
634 {
635  LockGuard guard(lock());
636 
637  for (auto& [key, value] : collection) {
638  const auto key_size = static_cast<size_t>(m_measure_key(key));
639  const auto value_size = static_cast<size_t>(m_measure_value(value));
640  CacheItem item{key_size, std::move(value), value_size};
641 
642  if (!m_constraint_policy->can_add(key, item)) {
643  return;
644  }
645 
646  insert_or_update(std::move(key), std::move(item));
647  }
648 }
649 
650 template<class K,
651  class V,
652  template<class, class, class>
653  class I,
654  template<class, class, class>
655  class E,
656  template<class, class, class>
657  class C,
658  class SV,
659  class SK,
660  class KH,
661  bool TS>
662 bool Cache<K, V, I, E, C, SV, SK, KH, TS>::check_insert(const K& key, const CacheItem& item)
663 {
664  if (m_constraint_policy->can_add(key, item)) {
665  return m_insertion_policy->should_add(key);
666  }
667 
668  // We need to perform some evictions to try and make some room.
669  // Since the insertion process can fail at anytime before we know how many keys to evict (e.g. if should_replace was to return false) however,
670  // we can't directly evict items as we go. To fix this, we copy the constraint policy to see how many keys we'd have to evict. If we manage to
671  // satisfy the constraint copy, we evict the keys we picked and proceed with the insertion.
672  auto constraint_copy = std::make_unique<MyConstraintPolicy>(*m_constraint_policy);
673  std::vector<DataMapIt> keys_to_evict;
674 
675  auto should_evict_another = [&](auto victim_it) { return victim_it != m_eviction_policy->victim_end() && !constraint_copy->can_add(key, item); };
676 
677  // As long as the constraint isn't satisfied, we keep evicting keys.
678  for (auto victim_it = m_eviction_policy->victim_begin(); should_evict_another(victim_it); ++victim_it) {
679  const K& key_to_evict = *victim_it;
680  auto key_and_item = m_data.find(key_to_evict);
681 
682  if (key_and_item != m_data.end()) {
683  if (!m_insertion_policy->should_replace(key_to_evict, key)) {
684  // This key to evict is considered "better" to have in cache than the item to be inserted.
685  // In this case, we abort the insertion.
686  return false;
687  }
688 
689  constraint_copy->on_evict(key_and_item->first, key_and_item->second);
690  keys_to_evict.push_back(key_and_item);
691  } else {
692  // If this trips, the eviction policy tried to evict an item not in cache: the eviction policy and the
693  // cache are out of sync.
694  assert(false);
695  }
696  }
697 
698  if (constraint_copy->can_add(key, item)) {
699  // The constraint is happy with that, so we actually evict the collected keys.
700  for (auto key_and_item : keys_to_evict) {
701  remove(key_and_item);
702  }
703  return true;
704  }
705 
706  return false;
707 }
708 
709 template<class K,
710  class V,
711  template<class, class, class>
712  class I,
713  template<class, class, class>
714  class E,
715  template<class, class, class>
716  class C,
717  class SV,
718  class SK,
719  class KH,
720  bool TS>
721 bool Cache<K, V, I, E, C, SV, SK, KH, TS>::check_replace(const K& key, const CacheItem& old_item, const CacheItem& new_item)
722 {
723  if (m_constraint_policy->can_replace(key, old_item, new_item)) {
724  return true;
725  }
726 
727  // The logic here is very similar to check_insert but with an important modification.
728  // Since in this code path we're updating an existing key instead of adding a new one, we need to handle the case
729  // where the eviction policy recommends the eviction of the key we're trying to insert. If this happens and the constraint
730  // is still not satisfied afterwards, we need to treat all subsequent constraint checks as if we were inserting instead of updating our key
731  // (because the original was evicted).
732  auto constraint_copy = std::make_unique<MyConstraintPolicy>(*m_constraint_policy);
733  bool evicted_original_key = false;
734 
735  auto can_replace = [&]() {
736  if (evicted_original_key) {
737  return constraint_copy->can_add(key, new_item);
738  } else {
739  return constraint_copy->can_replace(key, old_item, new_item);
740  }
741  };
742 
743  auto should_evict_another = [&](auto victim_it) { return victim_it != m_eviction_policy->victim_end() && !can_replace(); };
744 
745  // Evict until the constraint copy is happy.
746  std::vector<DataMapIt> keys_to_evict;
747 
748  for (auto victim_it = m_eviction_policy->victim_begin(); should_evict_another(victim_it); ++victim_it) {
749  const K& key_to_evict = *victim_it;
750  auto key_and_item = m_data.find(key_to_evict);
751 
752  if (key_and_item != m_data.end()) {
753  if (!m_insertion_policy->should_replace(key_to_evict, key)) {
754  return false;
755  }
756 
757  if (!evicted_original_key) {
758  evicted_original_key = key_and_item->first == key;
759  }
760 
761  constraint_copy->on_evict(key_and_item->first, key_and_item->second);
762  keys_to_evict.push_back(key_and_item);
763  } else {
764  // If this trips, the eviction policy tried to evict an item not in cache: the eviction policy and the
765  // cache are out of sync.
766  assert(false);
767  }
768  }
769 
770  if (can_replace()) {
771  for (auto key_and_item : keys_to_evict) {
772  remove(key_and_item);
773  }
774  return true;
775  }
776 
777  return false;
778 }
779 
780 template<class K,
781  class V,
782  template<class, class, class>
783  class I,
784  template<class, class, class>
785  class E,
786  template<class, class, class>
787  class C,
788  class SV,
789  class SK,
790  class KH,
791  bool TS>
792 void Cache<K, V, I, E, C, SV, SK, KH, TS>::insert_or_update(K&& key, CacheItem&& item)
793 {
794  auto key_and_item = m_data.find(key);
795  if (key_and_item != m_data.end()) {
796  using std::swap;
797  swap(key_and_item->second, item);
798  on_update(key_and_item->first, item, key_and_item->second);
799  } else {
800  // Insert.
801  const auto it_and_ok = m_data.insert_or_assign(std::move(key), std::move(item));
802  assert(it_and_ok.second);
803  on_insert(it_and_ok.first->first, it_and_ok.first->second);
804  }
805 }
806 
807 template<class K,
808  class V,
809  template<class, class, class>
810  class I,
811  template<class, class, class>
812  class E,
813  template<class, class, class>
814  class C,
815  class SV,
816  class SK,
817  class KH,
818  bool TS>
820 {
821  on_evict(it->first, it->second);
822  m_data.erase(it);
823 }
824 
825 template<class K,
826  class V,
827  template<class, class, class>
828  class I,
829  template<class, class, class>
830  class E,
831  template<class, class, class>
832  class C,
833  class SV,
834  class SK,
835  class KH,
836  bool TS>
837 void Cache<K, V, I, E, C, SV, SK, KH, TS>::on_insert(const K& key, const CacheItem& item) const
838 {
839  // Call event handler iif the method is defined in the policy.
840  boost::hana::if_(
841  detail::traits::event::has_on_insert<K, KH, V, I>,
842  [&](auto& x) { return x.on_insert(key, item); },
843  [](auto&) {})(*m_insertion_policy);
844 
845  boost::hana::if_(
846  detail::traits::event::has_on_insert<K, KH, V, E>,
847  [&](auto& x) { return x.on_insert(key, item); },
848  [](auto&) {})(*m_eviction_policy);
849 
850  boost::hana::if_(
851  detail::traits::event::has_on_insert<K, KH, V, C>,
852  [&](auto& x) { return x.on_insert(key, item); },
853  [](auto&) {})(*m_constraint_policy);
854 }
855 
856 template<class K,
857  class V,
858  template<class, class, class>
859  class I,
860  template<class, class, class>
861  class E,
862  template<class, class, class>
863  class C,
864  class SV,
865  class SK,
866  class KH,
867  bool TS>
868 void Cache<K, V, I, E, C, SV, SK, KH, TS>::on_update(const K& key, const CacheItem& old_item, const CacheItem& new_item) const
869 {
870  // Call event handler iif the method is defined in the policy.
871  boost::hana::if_(
872  detail::traits::event::has_on_update<K, KH, V, I>,
873  [&](auto& x) { return x.on_update(key, old_item, new_item); },
874  [](auto&) {})(*m_insertion_policy);
875 
876  boost::hana::if_(
877  detail::traits::event::has_on_update<K, KH, V, E>,
878  [&](auto& x) { return x.on_update(key, old_item, new_item); },
879  [](auto&) {})(*m_eviction_policy);
880 
881  boost::hana::if_(
882  detail::traits::event::has_on_update<K, KH, V, C>,
883  [&](auto& x) { return x.on_update(key, old_item, new_item); },
884  [](auto&) {})(*m_constraint_policy);
885 }
886 
887 template<class K,
888  class V,
889  template<class, class, class>
890  class I,
891  template<class, class, class>
892  class E,
893  template<class, class, class>
894  class C,
895  class SV,
896  class SK,
897  class KH,
898  bool TS>
899 void Cache<K, V, I, E, C, SV, SK, KH, TS>::on_cache_hit(const K& key, const CacheItem& item) const
900 {
901  // Update the cache hit rate accumulators.
902  m_hit_rate_acc(1);
903  m_byte_hit_rate_acc(static_cast<uint32_t>(item.m_value_size));
904 
905  // Call event handler iif the method is defined in the policy.
906  boost::hana::if_(
907  detail::traits::event::has_on_cachehit<K, KH, V, I>,
908  [&](auto& x) { return x.on_cache_hit(key, item); },
909  [](auto&) {})(*m_insertion_policy);
910 
911  boost::hana::if_(
912  detail::traits::event::has_on_cachehit<K, KH, V, E>,
913  [&](auto& x) { return x.on_cache_hit(key, item); },
914  [](auto&) {})(*m_eviction_policy);
915 
916  boost::hana::if_(
917  detail::traits::event::has_on_cachehit<K, KH, V, E>,
918  [&](auto& x) { return x.on_cache_hit(key, item); },
919  [](auto&) {})(*m_eviction_policy);
920 }
921 
922 template<class K,
923  class V,
924  template<class, class, class>
925  class I,
926  template<class, class, class>
927  class E,
928  template<class, class, class>
929  class C,
930  class SV,
931  class SK,
932  class KH,
933  bool TS>
934 template<class KeyView>
935 void Cache<K, V, I, E, C, SV, SK, KH, TS>::on_cache_miss(const KeyView& key) const
936 {
937  // Update the cache hit rate accumulators.
938  m_hit_rate_acc(0);
939  m_byte_hit_rate_acc(0);
940 
941  // Call event handler iif the method is defined in the policy.
942  boost::hana::if_(
943  detail::traits::event::has_on_cachemiss<K, KH, V, I>,
944  [&](auto& x) { return x.on_cache_miss(key); },
945  [](auto&) {})(*m_insertion_policy);
946 
947  boost::hana::if_(
948  detail::traits::event::has_on_cachemiss<K, KH, V, E>,
949  [&](auto& x) { return x.on_cache_miss(key); },
950  [](auto&) {})(*m_eviction_policy);
951 
952  boost::hana::if_(
953  detail::traits::event::has_on_cachemiss<K, KH, V, C>,
954  [&](auto& x) { return x.on_cache_miss(key); },
955  [](auto&) {})(*m_constraint_policy);
956 }
957 
958 template<class K,
959  class V,
960  template<class, class, class>
961  class I,
962  template<class, class, class>
963  class E,
964  template<class, class, class>
965  class C,
966  class SV,
967  class SK,
968  class KH,
969  bool TS>
970 void Cache<K, V, I, E, C, SV, SK, KH, TS>::on_evict(const K& key, const CacheItem& item) const
971 {
972  // Call event handler iif the method is defined in the policy.
973  boost::hana::if_(
974  detail::traits::event::has_on_evict<K, KH, V, I>,
975  [&](auto& x) { return x.on_evict(key, item); },
976  [](auto&) {})(*m_insertion_policy);
977 
978  boost::hana::if_(
979  detail::traits::event::has_on_evict<K, KH, V, E>,
980  [&](auto& x) { return x.on_evict(key, item); },
981  [](auto&) {})(*m_eviction_policy);
982 
983  boost::hana::if_(
984  detail::traits::event::has_on_evict<K, KH, V, C>,
985  [&](auto& x) { return x.on_evict(key, item); },
986  [](auto&) {})(*m_constraint_policy);
987 }
988 
989 template<class K,
990  class V,
991  template<class, class, class>
992  class I,
993  template<class, class, class>
994  class E,
995  template<class, class, class>
996  class C,
997  class SV,
998  class SK,
999  class KH,
1000  bool TS>
1001 void swap(Cache<K, V, I, E, C, SV, SK, KH, TS>& lhs, Cache<K, V, I, E, C, SV, SK, KH, TS>& rhs) noexcept
1002 {
1003  lhs.swap(rhs);
1004 }
1005 
1006 } // namespace cachemere
cachemere::Cache::insertion_policy
MyInsertionPolicy & insertion_policy()
Get a reference to the insertion policy used by the cache.
Definition: cache.hpp:389
cachemere::Cache::eviction_policy
MyEvictionPolicy & eviction_policy()
Get a reference to the eviction policy used by the cache.
Definition: cache.hpp:423
cachemere::Cache::remove
bool remove(const Key &key)
Remove a key and its value from the cache.
cachemere::Cache::swap
void swap(CacheType &other) noexcept
Swaps the current cache with another cache of the same type.
Definition: cache.hpp:288
cachemere::Cache::for_each
void for_each(F unary_function)
Apply a function to all objects in cache.
Definition: cache.hpp:268
cachemere::Cache::retain
void retain(P predicate_fn)
Retain all objects matching a predicate.
Definition: cache.hpp:240
cachemere::Item::m_value
Value m_value
The item stored in cache.
Definition: item.h:25
cachemere::Cache::clear
void clear()
Clears the cache contents.
Definition: cache.hpp:213
cachemere::Cache::byte_hit_rate
double byte_hit_rate() const
Compute and return the running byte hit rate of the cache, in bytes.
Definition: cache.hpp:508
cachemere::Item
A wrapper for items stored in the cache.
Definition: item.h:10
cachemere::Cache::number_of_items
size_t number_of_items() const
Get the number of items currently stored in the cache.
Definition: cache.hpp:336
cachemere::Cache::contains
bool contains(const KeyView &key) const
Check whether a given key is stored in the cache.
Definition: cache.hpp:66
cachemere::Cache::insert
bool insert(Key key, Value value)
Insert a key/value pair in the cache.
Definition: cache.hpp:148
cachemere::Cache::find
std::optional< Value > find(const KeyView &key) const
Find a given key in cache returning the associated value when it exists.
cachemere::Cache::hit_rate
double hit_rate() const
Compute and return the running hit rate of the cache.
Definition: cache.hpp:491
cachemere::Cache::update_constraint
void update_constraint(Args... args)
Update the cache constraint.
Definition: cache.hpp:355
cachemere::Cache
Thread-safe memory-restricted cache.
Definition: cache.h:63
cachemere
Root namespace.
Definition: cache.h:35
cachemere::Cache::constraint_policy
MyConstraintPolicy & constraint_policy()
Get a reference to the constraint policy used by the cache.
Definition: cache.hpp:457
cachemere::Cache::Cache
Cache(Args... args)
Simple constructor.
Definition: cache.hpp:16
cachemere::Cache::collect_into
void collect_into(C &container) const
Copy the cache contents in the provided container.
cachemere::Cache::statistics_window_size
uint32_t statistics_window_size() const
Get the size of the sliding window used for computing statistics.
Definition: cache.hpp:525