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 <utility>
9 :
10 : #include "Utilities/ErrorHandling/Assert.hpp"
11 : #include "Utilities/Gsl.hpp"
12 : #include "Utilities/Requires.hpp"
13 : #include "Utilities/TypeTraits/IsInteger.hpp"
14 :
15 : /// \ingroup UtilitiesGroup
16 : /// Range of integral values for StaticCache indices. The `Start` is inclusive
17 : /// and the `End` is exclusive. The range must not be empty.
18 : template <auto Start, auto End>
19 1 : struct CacheRange {
20 : static_assert(std::is_same_v<decltype(Start), decltype(End)>);
21 : static_assert(Start < End, "CacheRange must include at least one value");
22 0 : constexpr static auto start = Start;
23 0 : constexpr static auto end = End;
24 0 : constexpr static auto size = end - start;
25 0 : using value_type = std::remove_cv_t<decltype(start)>;
26 : };
27 :
28 : /// \ingroup UtilitiesGroup
29 : /// Possible enumeration values for the StaticCache. Only values specified here
30 : /// are retrievable.
31 : ///
32 : /// \note The `EnumerationType` must be streamable.
33 : template <typename EnumerationType, EnumerationType... Enums>
34 1 : struct CacheEnumeration {
35 0 : constexpr static size_t size = sizeof...(Enums);
36 0 : using value_type = EnumerationType;
37 : };
38 :
39 : /// \ingroup UtilitiesGroup
40 : /// A cache of objects intended to be stored in a static variable.
41 : ///
42 : /// Objects can be accessed via a combination of several `size_t` and `enum`
43 : /// arguments. The range of each integral argument is specified via a template
44 : /// parameter of type `CacheRange<start, end>`, giving the first and
45 : /// one-past-last values for the range. Each `enum` argument is specified by a
46 : /// template parameter of type `CacheEnumeration<EnumerationType, Members...>`
47 : /// giving the enumeration type and an explicit set of every enum member to be
48 : /// cached.
49 : ///
50 : /// \example
51 : /// A cache with only numeric indices:
52 : /// \snippet Test_StaticCache.cpp static_cache
53 : ///
54 : /// \example
55 : /// A cache with enumeration indices:
56 : /// \snippet Test_StaticCache.cpp static_cache_with_enum
57 : ///
58 : /// \example
59 : /// A cache with mixed numeric and enumeration indices:
60 : /// \snippet Test_StaticCache.cpp static_cache_with_enum_and_numeric
61 : ///
62 : /// \example
63 : /// A cache with no arguments at all (caching only a single object)
64 : /// \snippet Test_StaticCache.cpp static_cache_no_args
65 : ///
66 : /// \see make_static_cache
67 : ///
68 : /// \tparam T type held in the cache
69 : /// \tparam Ranges ranges of valid indices
70 : template <typename Generator, typename T, typename... Ranges>
71 1 : class StaticCache {
72 : public:
73 : template <typename Gen>
74 : // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
75 0 : explicit StaticCache(Gen&& generator)
76 : : generator_{std::forward<Gen>(generator)} {}
77 :
78 : template <typename... Args>
79 0 : const T& operator()(const Args... parameters) const {
80 : static_assert(sizeof...(parameters) == sizeof...(Ranges),
81 : "Number of arguments must match number of ranges.");
82 : return unwrap_cache(generate_tuple<Ranges>(parameters)...);
83 : }
84 :
85 : private:
86 : template <typename Range, typename T1,
87 : Requires<not std::is_enum<T1>::value> = nullptr>
88 0 : auto generate_tuple(const T1 parameter) const {
89 : static_assert(
90 : tt::is_integer_v<std::remove_cv_t<T1>>,
91 : "The parameter passed for a CacheRange must be an integer type.");
92 : return std::make_tuple(
93 : static_cast<typename Range::value_type>(parameter),
94 : std::integral_constant<typename Range::value_type, Range::start>{},
95 : std::make_integer_sequence<typename Range::value_type, Range::size>{});
96 : }
97 :
98 : template <typename Range, typename T1,
99 : Requires<std::is_enum<T1>::value> = nullptr>
100 0 : std::tuple<std::remove_cv_t<T1>, Range> generate_tuple(
101 : const T1 parameter) const {
102 : static_assert(
103 : std::is_same<typename Range::value_type, std::remove_cv_t<T1>>::value,
104 : "Mismatched enum parameter type and cached type.");
105 : return {parameter, Range{}};
106 : }
107 :
108 : template <typename... IntegralConstantValues>
109 0 : const T& unwrap_cache() const {
110 : static const T cached_object = generator_(IntegralConstantValues::value...);
111 : return cached_object;
112 : }
113 :
114 : template <typename... IntegralConstantValues, auto IndexOffset, auto... Is,
115 : typename... Args>
116 0 : const T& unwrap_cache(
117 : std::tuple<
118 : std::remove_cv_t<decltype(IndexOffset)>,
119 : std::integral_constant<std::remove_cv_t<decltype(IndexOffset)>,
120 : IndexOffset>,
121 : std::integer_sequence<std::remove_cv_t<decltype(IndexOffset)>, Is...>>
122 : parameter0,
123 : Args... parameters) const {
124 : if (UNLIKELY(IndexOffset > std::get<0>(parameter0) or
125 : std::get<0>(parameter0) >=
126 : IndexOffset +
127 : static_cast<decltype(IndexOffset)>(sizeof...(Is)))) {
128 : ERROR("Index out of range: "
129 : << IndexOffset << " <= " << std::get<0>(parameter0) << " < "
130 : << IndexOffset + static_cast<decltype(IndexOffset)>(sizeof...(Is)));
131 : }
132 : // note that the act of assigning to the specified function pointer type
133 : // fixes the template arguments that need to be inferred.
134 : static const std::array<
135 : const T& (StaticCache<Generator, T, Ranges...>::*)(Args...) const,
136 : sizeof...(Is)>
137 : cache{{&StaticCache<Generator, T, Ranges...>::unwrap_cache<
138 : IntegralConstantValues...,
139 : std::integral_constant<decltype(IndexOffset),
140 : Is + IndexOffset>>...}};
141 : // The array `cache` holds pointers to member functions, so we dereference
142 : // the pointer and invoke it on `this`.
143 : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
144 : #pragma GCC diagnostic push
145 : #pragma GCC diagnostic ignored "-Warray-bounds"
146 : #endif
147 : return (this->*gsl::at(cache, std::get<0>(parameter0) - IndexOffset))(
148 : parameters...);
149 : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
150 : #pragma GCC diagnostic pop
151 : #endif
152 : }
153 :
154 : template <typename... IntegralConstantValues, typename EnumType,
155 : EnumType... EnumValues, typename... Args>
156 0 : const T& unwrap_cache(
157 : std::tuple<EnumType, CacheEnumeration<EnumType, EnumValues...>>
158 : parameter0,
159 : Args... parameters) const {
160 : size_t array_location = std::numeric_limits<size_t>::max();
161 : static const std::array<EnumType, sizeof...(EnumValues)> values{
162 : {EnumValues...}};
163 : for (size_t i = 0; i < sizeof...(EnumValues); ++i) {
164 : if (std::get<0>(parameter0) == gsl::at(values, i)) {
165 : array_location = i;
166 : break;
167 : }
168 : }
169 : if (UNLIKELY(array_location == std::numeric_limits<size_t>::max())) {
170 : ERROR("Uncached enumeration value: " << std::get<0>(parameter0));
171 : }
172 : // note that the act of assigning to the specified function pointer type
173 : // fixes the template arguments that need to be inferred.
174 : static const std::array<
175 : const T& (StaticCache<Generator, T, Ranges...>::*)(Args...) const,
176 : sizeof...(EnumValues)>
177 : cache{{&StaticCache<Generator, T, Ranges...>::unwrap_cache<
178 : IntegralConstantValues...,
179 : std::integral_constant<EnumType, EnumValues>>...}};
180 : // The array `cache` holds pointers to member functions, so we dereference
181 : // the pointer and invoke it on `this`.
182 : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
183 : #pragma GCC diagnostic push
184 : #pragma GCC diagnostic ignored "-Warray-bounds"
185 : #endif
186 : return (this->*gsl::at(cache, array_location))(parameters...);
187 : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
188 : #pragma GCC diagnostic pop
189 : #endif
190 : }
191 :
192 0 : const Generator generator_;
193 : };
194 :
195 : /// \ingroup UtilitiesGroup
196 : /// Create a StaticCache, inferring the cached type from the generator.
197 : template <typename... Ranges, typename Generator>
198 1 : auto make_static_cache(Generator&& generator) {
199 : using CachedType = std::remove_cv_t<decltype(generator(
200 : std::declval<typename Ranges::value_type>()...))>;
201 : return StaticCache<std::remove_cv_t<Generator>, CachedType, Ranges...>(
202 : std::forward<Generator>(generator));
203 : }
|