SpECTRE Documentation Coverage Report
Current view: top level - Utilities - StaticCache.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 17.4 % 23 4
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            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/TMPL.hpp"
      14              : #include "Utilities/TypeTraits/IsInteger.hpp"
      15              : 
      16              : /// \ingroup UtilitiesGroup
      17              : /// Range of integral values for StaticCache indices.  The `Start` is inclusive
      18              : /// and the `End` is exclusive.  The range must not be empty.
      19              : template <auto Start, auto End>
      20            1 : struct CacheRange {
      21              :   static_assert(std::is_same_v<decltype(Start), decltype(End)>);
      22              :   static_assert(Start < End, "CacheRange must include at least one value");
      23            0 :   constexpr static auto start = Start;
      24            0 :   constexpr static auto end = End;
      25            0 :   constexpr static auto size = end - start;
      26            0 :   using value_type = std::remove_cv_t<decltype(start)>;
      27              : };
      28              : 
      29              : /// \ingroup UtilitiesGroup
      30              : /// Possible enumeration values for the StaticCache. Only values specified here
      31              : /// are retrievable.
      32              : ///
      33              : /// \note The `EnumerationType` must be streamable.
      34              : template <typename EnumerationType, EnumerationType... Enums>
      35            1 : struct CacheEnumeration {
      36            0 :   constexpr static size_t size = sizeof...(Enums);
      37            0 :   using value_type = EnumerationType;
      38            0 :   static constexpr std::array<value_type, size> values{Enums...};
      39            0 :   using value_list = tmpl::integral_list<EnumerationType, Enums...>;
      40              : };
      41              : 
      42              : /// \ingroup UtilitiesGroup
      43              : /// A cache of objects intended to be stored in a static variable.
      44              : ///
      45              : /// Objects can be accessed via a combination of several `size_t` and `enum`
      46              : /// arguments. The range of each integral argument is specified via a template
      47              : /// parameter of type `CacheRange<start, end>`, giving the first and
      48              : /// one-past-last values for the range. Each `enum` argument is specified by a
      49              : /// template parameter of type `CacheEnumeration<EnumerationType, Members...>`
      50              : /// giving the enumeration type and an explicit set of every enum member to be
      51              : /// cached.
      52              : ///
      53              : /// \example
      54              : /// A cache with only numeric indices:
      55              : /// \snippet Test_StaticCache.cpp static_cache
      56              : ///
      57              : /// \example
      58              : /// A cache with enumeration indices:
      59              : /// \snippet Test_StaticCache.cpp static_cache_with_enum
      60              : ///
      61              : /// \example
      62              : /// A cache with mixed numeric and enumeration indices:
      63              : /// \snippet Test_StaticCache.cpp static_cache_with_enum_and_numeric
      64              : ///
      65              : /// \example
      66              : /// A cache with no arguments at all (caching only a single object)
      67              : /// \snippet Test_StaticCache.cpp static_cache_no_args
      68              : ///
      69              : /// \see make_static_cache
      70              : ///
      71              : /// \tparam T type held in the cache
      72              : /// \tparam Ranges ranges of valid indices
      73              : template <typename Generator, typename T, typename... Ranges>
      74            1 : class StaticCache {
      75              :  public:
      76              :   template <typename Gen>
      77              :   // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
      78            0 :   explicit StaticCache(Gen&& generator)
      79              :       : generator_{std::forward<Gen>(generator)} {}
      80              : 
      81              :   template <typename... Args>
      82            0 :   const T& operator()(const Args... parameters) const {
      83              :     static_assert(sizeof...(parameters) == sizeof...(Ranges),
      84              :                   "Number of arguments must match number of ranges.");
      85              :     return unwrap_cache_combined(generate_tuple<Ranges>(parameters)...);
      86              :   }
      87              : 
      88              :  private:
      89              :   template <typename Range, typename T1>
      90            0 :   auto generate_tuple(const T1 parameter) const {
      91              :     if constexpr (std::is_enum<T1>::value) {
      92              :       static_assert(
      93              :           std::is_same<typename Range::value_type, std::remove_cv_t<T1>>::value,
      94              :           "Mismatched enum parameter type and cached type.");
      95              :       size_t array_location = std::numeric_limits<size_t>::max();
      96              :       static const std::array<typename Range::value_type, Range::size> values{
      97              :           Range::values};
      98              :       for (size_t i = 0; i < Range::size; ++i) {
      99              :         if (parameter == gsl::at(values, i)) {
     100              :           array_location = i;
     101              :           break;
     102              :         }
     103              :       }
     104              :       if (UNLIKELY(array_location == std::numeric_limits<size_t>::max())) {
     105              :         ERROR("Uncached enumeration value: " << parameter);
     106              :       }
     107              :       return std::tuple{array_location, typename Range::value_list{}};
     108              :     } else {
     109              :       static_assert(
     110              :           tt::is_integer_v<std::remove_cv_t<T1>>,
     111              :           "The parameter passed for a CacheRange must be an integer type.");
     112              : 
     113              :       // Check range here because the nested range checks in the unwrap_cache
     114              :       // function cause significant compile time overhead.
     115              :       if (UNLIKELY(Range::start >
     116              :                        static_cast<decltype(Range::start)>(parameter) or
     117              :                    static_cast<decltype(Range::start)>(parameter) >=
     118              :                        Range::start +
     119              :                            static_cast<decltype(Range::start)>(Range::size))) {
     120              :         ERROR("Index out of range: "
     121              :               << Range::start << " <= " << parameter << " < "
     122              :               << Range::start +
     123              :                      static_cast<decltype(Range::start)>(Range::size));
     124              :       }
     125              :       return std::tuple{
     126              :           // unsigned cast is safe since this is an index into an array
     127              :           static_cast<size_t>(
     128              :               static_cast<typename Range::value_type>(parameter) -
     129              :               Range::start),
     130              :           tmpl::make_sequence<
     131              :               tmpl::integral_constant<typename Range::value_type, Range::start>,
     132              :               Range::size>{}};
     133              :     }
     134              :   }
     135              : 
     136              :   // Compilation time notes:
     137              :   //
     138              :   // - The separate peeling of different number of arguments is the
     139              :   //   fastest implementation Nils Deppe has found so far.
     140              :   // - The second fastest is using a Cartesian product on the lists of
     141              :   //   possible values, followed by a for_each over that list to set the
     142              :   //   function pointers in the array. Note that having the for_each function
     143              :   //   be marked `constexpr` resulted in a 6x reduction in compilation time
     144              :   //   for clang 17 compared to the not constexpr version, but still 40%
     145              :   //   slower compilation compared to the pattern matching below.
     146              :   template <typename... IntegralConstantValues>
     147            0 :   const T& unwrap_cache_combined() const {
     148              :     static const T cached_object = generator_(IntegralConstantValues::value...);
     149              :     return cached_object;
     150              :   }
     151              : 
     152              :   // NVCC v12.6 can't handle the unwrapping code below, so we use a simpler
     153              :   // version for CUDA. Note that we don't actually need to use StaticCache on
     154              :   // device at all, so the main issue with NVCC is that it tries to compile this
     155              :   // code for CUDA at all.
     156              : #if defined(__NVCC__) && defined(__CUDA_ARCH__)
     157              :   template <typename... IntegralConstantValues, typename... IntegralConstants,
     158              :             typename... Args>
     159              :   const T& unwrap_cache_combined(
     160              :       std::tuple<size_t, tmpl::list<IntegralConstants...>> parameter0,
     161              :       Args... parameters) const {
     162              :     // note that the act of assigning to the specified function pointer type
     163              :     // fixes the template arguments that need to be inferred.
     164              :     static const std::array<
     165              :         const T& (StaticCache<Generator, T, Ranges...>::*)(Args...) const,
     166              :         sizeof...(IntegralConstants)>
     167              :         cache{{&StaticCache<Generator, T, Ranges...>::unwrap_cache_combined<
     168              :             IntegralConstantValues..., IntegralConstants>...}};
     169              :     // The array `cache` holds pointers to member functions, so we dereference
     170              :     // the pointer and invoke it on `this`.
     171              :     return (this->*gsl::at(cache, std::get<0>(parameter0)))(parameters...);
     172              :   }
     173              : #else  // defined(__NVCC__) && defined(__CUDA_ARCH__)
     174              :   template <typename... IntegralConstantValues, typename... IntegralConstants>
     175            0 :   const T& unwrap_cache_combined(
     176              :       std::tuple<size_t, tmpl::list<IntegralConstants...>> parameter0) const {
     177              :     // note that the act of assigning to the specified function pointer type
     178              :     // fixes the template arguments that need to be inferred.
     179              :     static const std::array<const T& (StaticCache::*)() const,
     180              :                             sizeof...(IntegralConstants)>
     181              :         cache{{&StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     182              :                                                    IntegralConstants>...}};
     183              :     // The array `cache` holds pointers to member functions, so we dereference
     184              :     // the pointer and invoke it on `this`.
     185              :     return (this->*gsl::at(cache, std::get<0>(parameter0)))();
     186              :   }
     187              : 
     188              :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     189              :             typename... IntegralConstants1>
     190            0 :   const T& unwrap_cache_combined(
     191              :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     192              :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1) const {
     193              :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     194              :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     195              :     constexpr size_t total_size = num0 * num1;
     196              :     // note that the act of assigning to the specified function pointer type
     197              :     // fixes the template arguments that need to be inferred.
     198              :     static const std::array<const T& (StaticCache::*)() const, total_size>
     199              :         cache = []() {
     200              :           std::array<const T& (StaticCache::*)() const, total_size> result;
     201              :           size_t counter1 = 0;
     202              :           const auto helper1 = [&counter1,
     203              :                                 &result]<typename IntegralConstant1>() {
     204              :             size_t counter0 = 0;
     205              :             const auto helper0 = [&counter0, &counter1,
     206              :                                   &result]<typename IntegralConstant0>() {
     207              :               result[counter0 + num0 * counter1] =
     208              :                   &StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     209              :                                                       IntegralConstant0,
     210              :                                                       IntegralConstant1>;
     211              :               ++counter0;
     212              :             };
     213              :             EXPAND_PACK_LEFT_TO_RIGHT(
     214              :                 helper0.template operator()<IntegralConstants0>());
     215              :             ++counter1;
     216              :           };
     217              :           EXPAND_PACK_LEFT_TO_RIGHT(
     218              :               helper1.template operator()<IntegralConstants1>());
     219              :           return result;
     220              :         }();
     221              : 
     222              :     // The array `cache` holds pointers to member functions, so we dereference
     223              :     // the pointer and invoke it on `this`.
     224              :     return (this->*gsl::at(cache, std::get<0>(parameter0) +
     225              :                                       num0 * std::get<0>(parameter1)))();
     226              :   }
     227              : 
     228              :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     229              :             typename... IntegralConstants1, typename... IntegralConstants2>
     230            0 :   const T& unwrap_cache_combined(
     231              :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     232              :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     233              :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2) const {
     234              :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     235              :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     236              :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     237              :     constexpr size_t total_size = num0 * num1 * num2;
     238              :     // note that the act of assigning to the specified function pointer type
     239              :     // fixes the template arguments that need to be inferred.
     240              :     static const std::array<const T& (StaticCache::*)() const, total_size>
     241              :         cache = []() {
     242              :           std::array<const T& (StaticCache::*)() const, total_size> result;
     243              :           size_t counter2 = 0;
     244              :           const auto helper2 = [&counter2,
     245              :                                 &result]<typename IntegralConstant2>() {
     246              :             size_t counter1 = 0;
     247              :             const auto helper1 = [&counter1, &counter2,
     248              :                                   &result]<typename IntegralConstant1>() {
     249              :               size_t counter0 = 0;
     250              :               const auto helper0 = [&counter0, &counter1, &counter2,
     251              :                                     &result]<typename IntegralConstant0>() {
     252              :                 result[counter0 + num0 * (counter1 + num1 * counter2)] =
     253              :                     &StaticCache::unwrap_cache_combined<
     254              :                         IntegralConstantValues..., IntegralConstant0,
     255              :                         IntegralConstant1, IntegralConstant2>;
     256              :                 ++counter0;
     257              :               };
     258              :               EXPAND_PACK_LEFT_TO_RIGHT(
     259              :                   helper0.template operator()<IntegralConstants0>());
     260              :               ++counter1;
     261              :             };
     262              :             EXPAND_PACK_LEFT_TO_RIGHT(
     263              :                 helper1.template operator()<IntegralConstants1>());
     264              :             ++counter2;
     265              :           };
     266              :           EXPAND_PACK_LEFT_TO_RIGHT(
     267              :               helper2.template operator()<IntegralConstants2>());
     268              :           return result;
     269              :         }();
     270              : 
     271              :     // The array `cache` holds pointers to member functions, so we dereference
     272              :     // the pointer and invoke it on `this`.
     273              :     return (
     274              :         this->*gsl::at(cache, std::get<0>(parameter0) +
     275              :                                   num0 * (std::get<0>(parameter1) +
     276              :                                           num1 * std::get<0>(parameter2))))();
     277              :   }
     278              : 
     279              :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     280              :             typename... IntegralConstants1, typename... IntegralConstants2,
     281              :             typename... IntegralConstants3>
     282            0 :   const T& unwrap_cache_combined(
     283              :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     284              :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     285              :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     286              :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3) const {
     287              :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     288              :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     289              :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     290              :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     291              :     constexpr size_t total_size = num0 * num1 * num2 * num3;
     292              :     // note that the act of assigning to the specified function pointer type
     293              :     // fixes the template arguments that need to be inferred.
     294              :     static const std::array<const T& (StaticCache::*)() const, total_size>
     295              :         cache = []() {
     296              :           std::array<const T& (StaticCache::*)() const, total_size> result;
     297              :           size_t counter3 = 0;
     298              :           const auto helper3 = [&counter3,
     299              :                                 &result]<typename IntegralConstant3>() {
     300              :             size_t counter2 = 0;
     301              :             const auto helper2 = [&counter2, &counter3,
     302              :                                   &result]<typename IntegralConstant2>() {
     303              :               size_t counter1 = 0;
     304              :               const auto helper1 = [&counter1, &counter2, &counter3,
     305              :                                     &result]<typename IntegralConstant1>() {
     306              :                 size_t counter0 = 0;
     307              :                 const auto helper0 = [&counter0, &counter1, &counter2,
     308              :                                       &counter3,
     309              :                                       &result]<typename IntegralConstant0>() {
     310              :                   result[counter0 +
     311              :                          num0 *
     312              :                              (counter1 + num1 * (counter2 + num2 * counter3))] =
     313              :                       &StaticCache::unwrap_cache_combined<
     314              :                           IntegralConstantValues..., IntegralConstant0,
     315              :                           IntegralConstant1, IntegralConstant2,
     316              :                           IntegralConstant3>;
     317              :                   ++counter0;
     318              :                 };
     319              :                 EXPAND_PACK_LEFT_TO_RIGHT(
     320              :                     helper0.template operator()<IntegralConstants0>());
     321              :                 ++counter1;
     322              :               };
     323              :               EXPAND_PACK_LEFT_TO_RIGHT(
     324              :                   helper1.template operator()<IntegralConstants1>());
     325              :               ++counter2;
     326              :             };
     327              :             EXPAND_PACK_LEFT_TO_RIGHT(
     328              :                 helper2.template operator()<IntegralConstants2>());
     329              :             ++counter3;
     330              :           };
     331              :           EXPAND_PACK_LEFT_TO_RIGHT(
     332              :               helper3.template operator()<IntegralConstants3>());
     333              :           return result;
     334              :         }();
     335              : 
     336              :     // The array `cache` holds pointers to member functions, so we
     337              :     // dereference the pointer and invoke it on `this`.
     338              :     return (
     339              :         this->*gsl::at(cache,
     340              :                        std::get<0>(parameter0) +
     341              :                            num0 * (std::get<0>(parameter1) +
     342              :                                    num1 * (std::get<0>(parameter2) +
     343              :                                            num2 * std::get<0>(parameter3)))))();
     344              :   }
     345              : 
     346              :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     347              :             typename... IntegralConstants1, typename... IntegralConstants2,
     348              :             typename... IntegralConstants3, typename... IntegralConstants4,
     349              :             typename... Args>
     350            0 :   const T& unwrap_cache_combined(
     351              :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     352              :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     353              :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     354              :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3,
     355              :       std::tuple<size_t, tmpl::list<IntegralConstants4...>> parameter4,
     356              :       const Args&... parameters) const {
     357              :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     358              :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     359              :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     360              :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     361              :     constexpr size_t num4 = sizeof...(IntegralConstants4);
     362              :     constexpr size_t total_size = num0 * num1 * num2 * num3 * num4;
     363              :     // note that the act of assigning to the specified function pointer type
     364              :     // fixes the template arguments that need to be inferred.
     365              :     static const std::array<const T& (StaticCache::*)(Args...) const,
     366              :                             total_size>
     367              :         cache = []() {
     368              :           std::array<const T& (StaticCache::*)(Args...) const, total_size>
     369              :               result;
     370              :           size_t counter4 = 0;
     371              :           const auto helper4 = [&counter4,
     372              :                                 &result]<typename IntegralConstant4>() {
     373              :             size_t counter3 = 0;
     374              :             const auto helper3 = [&counter3, &counter4,
     375              :                                   &result]<typename IntegralConstant3>() {
     376              :               size_t counter2 = 0;
     377              :               const auto helper2 = [&counter2, &counter3, &counter4,
     378              :                                     &result]<typename IntegralConstant2>() {
     379              :                 size_t counter1 = 0;
     380              :                 const auto helper1 = [&counter1, &counter2, &counter3,
     381              :                                       &counter4,
     382              :                                       &result]<typename IntegralConstant1>() {
     383              :                   size_t counter0 = 0;
     384              :                   const auto helper0 = [&counter0, &counter1, &counter2,
     385              :                                         &counter3, &counter4,
     386              :                                         &result]<typename IntegralConstant0>() {
     387              :                     result[counter0 +
     388              :                            num0 *
     389              :                                (counter1 +
     390              :                                 num1 * (counter2 +
     391              :                                         num2 * (counter3 + num3 * counter4)))] =
     392              :                         &StaticCache::unwrap_cache_combined<
     393              :                             IntegralConstantValues..., IntegralConstant0,
     394              :                             IntegralConstant1, IntegralConstant2,
     395              :                             IntegralConstant3, IntegralConstant4>;
     396              :                     ++counter0;
     397              :                   };
     398              :                   EXPAND_PACK_LEFT_TO_RIGHT(
     399              :                       helper0.template operator()<IntegralConstants0>());
     400              :                   ++counter1;
     401              :                 };
     402              :                 EXPAND_PACK_LEFT_TO_RIGHT(
     403              :                     helper1.template operator()<IntegralConstants1>());
     404              :                 ++counter2;
     405              :               };
     406              :               EXPAND_PACK_LEFT_TO_RIGHT(
     407              :                   helper2.template operator()<IntegralConstants2>());
     408              :               ++counter3;
     409              :             };
     410              :             EXPAND_PACK_LEFT_TO_RIGHT(
     411              :                 helper3.template operator()<IntegralConstants3>());
     412              :             ++counter4;
     413              :           };
     414              :           EXPAND_PACK_LEFT_TO_RIGHT(
     415              :               helper4.template operator()<IntegralConstants4>());
     416              :           return result;
     417              :         }();
     418              : 
     419              :     // The array `cache` holds pointers to member functions, so we dereference
     420              :     // the pointer and invoke it on `this`.
     421              :     return (
     422              :         this->*gsl::at(
     423              :                    cache,
     424              :                    std::get<0>(parameter0) +
     425              :                        num0 *
     426              :                            (std::get<0>(parameter1) +
     427              :                             num1 * (std::get<0>(parameter2) +
     428              :                                     num2 * (std::get<0>(parameter3) +
     429              :                                             num3 * std::get<0>(parameter4))))))(
     430              :         parameters...);
     431              :   }
     432              : #endif  // defined(__NVCC__) && defined(__CUDA_ARCH__)
     433              : 
     434            0 :   const Generator generator_;
     435              : };
     436              : 
     437              : /// \ingroup UtilitiesGroup
     438              : /// Create a StaticCache, inferring the cached type from the generator.
     439              : template <typename... Ranges, typename Generator>
     440            1 : auto make_static_cache(Generator&& generator) {
     441              :   using CachedType = std::remove_cvref_t<decltype(generator(
     442              :       std::declval<typename Ranges::value_type>()...))>;
     443              :   return StaticCache<std::remove_cvref_t<Generator>, CachedType, Ranges...>(
     444              :       std::forward<Generator>(generator));
     445              : }
        

Generated by: LCOV version 2.0-1