Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <array>
7 : #include <cstddef>
8 : #include <mutex>
9 : #include <optional>
10 : #include <type_traits>
11 : #include <utility>
12 :
13 : #include "Utilities/ErrorHandling/Error.hpp"
14 : #include "Utilities/Gsl.hpp"
15 : #include "Utilities/StaticCache.hpp"
16 : #include "Utilities/TypeTraits/IsInteger.hpp"
17 :
18 : /// \ingroup UtilitiesGroup
19 : /// A cache that selects and lazily initializes entries at runtime.
20 : ///
21 : /// Unlike `StaticCache`, this class does not instantiate the generator once for
22 : /// every combination of indices. This reduces compile time for large caches at
23 : /// the cost of runtime index calculation and synchronization.
24 : ///
25 : /// \note `RuntimeCache` is tested in `Test_StaticCache.cpp`.
26 : template <typename Generator, typename T, typename... Ranges>
27 1 : class RuntimeCache {
28 : public:
29 0 : explicit RuntimeCache(Generator generator)
30 : : generator_{std::move(generator)} {}
31 :
32 : template <typename... Args>
33 0 : const T& operator()(const Args... parameters) const {
34 : static_assert(sizeof...(parameters) == sizeof...(Ranges),
35 : "Number of arguments must match number of ranges.");
36 : size_t array_location = 0;
37 : ((array_location = array_location * static_cast<size_t>(Ranges::size) +
38 : index<Ranges>(parameters)),
39 : ...);
40 : std::call_once(gsl::at(initialized_, array_location), [this, array_location,
41 : ¶meters...]() {
42 : gsl::at(cached_objects_, array_location)
43 : .emplace(generator_(
44 : static_cast<typename Ranges::value_type>(parameters)...));
45 : });
46 : return gsl::at(cached_objects_, array_location).value();
47 : }
48 :
49 : private:
50 : template <typename Range, typename U>
51 0 : static size_t index(const U parameter) {
52 : if constexpr (std::is_enum_v<U>) {
53 : static_assert(
54 : std::is_same_v<typename Range::value_type, std::remove_cv_t<U>>,
55 : "Mismatched enum parameter type and cached type.");
56 : for (size_t i = 0; i < Range::size; ++i) {
57 : if (parameter == gsl::at(Range::values, i)) {
58 : return i;
59 : }
60 : }
61 : ERROR("Uncached enumeration value: " << parameter);
62 : } else {
63 : static_assert(
64 : tt::is_integer_v<std::remove_cv_t<U>>,
65 : "The parameter passed for a CacheRange must be an integer type.");
66 : if (UNLIKELY(
67 : Range::start > static_cast<decltype(Range::start)>(parameter) or
68 : static_cast<decltype(Range::start)>(parameter) >= Range::end)) {
69 : ERROR("Index out of range: " << Range::start << " <= " << parameter
70 : << " < " << Range::end);
71 : }
72 : return static_cast<size_t>(
73 : static_cast<typename Range::value_type>(parameter) - Range::start);
74 : }
75 : }
76 :
77 0 : static constexpr size_t number_of_entries =
78 : (size_t{1} * ... * static_cast<size_t>(Ranges::size));
79 0 : Generator generator_;
80 : // NOLINTNEXTLINE(spectre-mutable)
81 0 : mutable std::array<std::once_flag, number_of_entries> initialized_{};
82 : // NOLINTNEXTLINE(spectre-mutable)
83 0 : mutable std::array<std::optional<T>, number_of_entries> cached_objects_{};
84 : };
85 :
86 : /// \ingroup UtilitiesGroup
87 : /// Create a RuntimeCache, inferring the cached type from the generator.
88 : template <typename... Ranges, typename Generator>
89 1 : auto make_runtime_cache(Generator&& generator) {
90 : using CachedType = std::remove_cvref_t<decltype(generator(
91 : std::declval<typename Ranges::value_type>()...))>;
92 : return RuntimeCache<std::remove_cvref_t<Generator>, CachedType, Ranges...>(
93 : std::forward<Generator>(generator));
94 : }
|