SpECTRE Documentation Coverage Report
Current view: top level - Utilities - StaticCache.hpp Hit Total Coverage
Commit: 7b84572e7f3b8c8fb2d105e3d4e12204a244a350 Lines: 4 23 17.4 %
Date: 2025-04-18 14:09:55
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             : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
     153             : #pragma GCC diagnostic push
     154             : #pragma GCC diagnostic ignored "-Warray-bounds"
     155             : #endif
     156             :   template <typename... IntegralConstantValues, typename... IntegralConstants>
     157           0 :   const T& unwrap_cache_combined(
     158             :       std::tuple<size_t, tmpl::list<IntegralConstants...>> parameter0) const {
     159             :     // note that the act of assigning to the specified function pointer type
     160             :     // fixes the template arguments that need to be inferred.
     161             :     static const std::array<const T& (StaticCache::*)() const,
     162             :                             sizeof...(IntegralConstants)>
     163             :         cache{{&StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     164             :                                                    IntegralConstants>...}};
     165             :     // The array `cache` holds pointers to member functions, so we dereference
     166             :     // the pointer and invoke it on `this`.
     167             :     return (this->*gsl::at(cache, std::get<0>(parameter0)))();
     168             :   }
     169             : 
     170             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     171             :             typename... IntegralConstants1>
     172           0 :   const T& unwrap_cache_combined(
     173             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     174             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1) const {
     175             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     176             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     177             :     constexpr size_t total_size = num0 * num1;
     178             :     // note that the act of assigning to the specified function pointer type
     179             :     // fixes the template arguments that need to be inferred.
     180             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     181             :         cache = []() {
     182             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     183             :           size_t counter1 = 0;
     184             :           const auto helper1 = [&counter1,
     185             :                                 &result]<typename IntegralConstant1>() {
     186             :             size_t counter0 = 0;
     187             :             const auto helper0 = [&counter0, &counter1,
     188             :                                   &result]<typename IntegralConstant0>() {
     189             :               result[counter0 + num0 * counter1] =
     190             :                   &StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     191             :                                                       IntegralConstant0,
     192             :                                                       IntegralConstant1>;
     193             :               ++counter0;
     194             :             };
     195             :             EXPAND_PACK_LEFT_TO_RIGHT(
     196             :                 helper0.template operator()<IntegralConstants0>());
     197             :             ++counter1;
     198             :           };
     199             :           EXPAND_PACK_LEFT_TO_RIGHT(
     200             :               helper1.template operator()<IntegralConstants1>());
     201             :           return result;
     202             :         }();
     203             : 
     204             :     // The array `cache` holds pointers to member functions, so we dereference
     205             :     // the pointer and invoke it on `this`.
     206             :     return (this->*gsl::at(cache, std::get<0>(parameter0) +
     207             :                                       num0 * std::get<0>(parameter1)))();
     208             :   }
     209             : 
     210             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     211             :             typename... IntegralConstants1, typename... IntegralConstants2>
     212           0 :   const T& unwrap_cache_combined(
     213             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     214             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     215             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2) const {
     216             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     217             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     218             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     219             :     constexpr size_t total_size = num0 * num1 * num2;
     220             :     // note that the act of assigning to the specified function pointer type
     221             :     // fixes the template arguments that need to be inferred.
     222             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     223             :         cache = []() {
     224             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     225             :           size_t counter2 = 0;
     226             :           const auto helper2 = [&counter2,
     227             :                                 &result]<typename IntegralConstant2>() {
     228             :             size_t counter1 = 0;
     229             :             const auto helper1 = [&counter1, &counter2,
     230             :                                   &result]<typename IntegralConstant1>() {
     231             :               size_t counter0 = 0;
     232             :               const auto helper0 = [&counter0, &counter1, &counter2,
     233             :                                     &result]<typename IntegralConstant0>() {
     234             :                 result[counter0 + num0 * (counter1 + num1 * counter2)] =
     235             :                     &StaticCache::unwrap_cache_combined<
     236             :                         IntegralConstantValues..., IntegralConstant0,
     237             :                         IntegralConstant1, IntegralConstant2>;
     238             :                 ++counter0;
     239             :               };
     240             :               EXPAND_PACK_LEFT_TO_RIGHT(
     241             :                   helper0.template operator()<IntegralConstants0>());
     242             :               ++counter1;
     243             :             };
     244             :             EXPAND_PACK_LEFT_TO_RIGHT(
     245             :                 helper1.template operator()<IntegralConstants1>());
     246             :             ++counter2;
     247             :           };
     248             :           EXPAND_PACK_LEFT_TO_RIGHT(
     249             :               helper2.template operator()<IntegralConstants2>());
     250             :           return result;
     251             :         }();
     252             : 
     253             :     // The array `cache` holds pointers to member functions, so we dereference
     254             :     // the pointer and invoke it on `this`.
     255             :     return (
     256             :         this->*gsl::at(cache, std::get<0>(parameter0) +
     257             :                                   num0 * (std::get<0>(parameter1) +
     258             :                                           num1 * std::get<0>(parameter2))))();
     259             :   }
     260             : 
     261             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     262             :             typename... IntegralConstants1, typename... IntegralConstants2,
     263             :             typename... IntegralConstants3>
     264           0 :   const T& unwrap_cache_combined(
     265             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     266             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     267             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     268             :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3) const {
     269             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     270             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     271             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     272             :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     273             :     constexpr size_t total_size = num0 * num1 * num2 * num3;
     274             :     // note that the act of assigning to the specified function pointer type
     275             :     // fixes the template arguments that need to be inferred.
     276             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     277             :         cache = []() {
     278             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     279             :           size_t counter3 = 0;
     280             :           const auto helper3 = [&counter3,
     281             :                                 &result]<typename IntegralConstant3>() {
     282             :             size_t counter2 = 0;
     283             :             const auto helper2 = [&counter2, &counter3,
     284             :                                   &result]<typename IntegralConstant2>() {
     285             :               size_t counter1 = 0;
     286             :               const auto helper1 = [&counter1, &counter2, &counter3,
     287             :                                     &result]<typename IntegralConstant1>() {
     288             :                 size_t counter0 = 0;
     289             :                 const auto helper0 = [&counter0, &counter1, &counter2,
     290             :                                       &counter3,
     291             :                                       &result]<typename IntegralConstant0>() {
     292             :                   result[counter0 +
     293             :                          num0 *
     294             :                              (counter1 + num1 * (counter2 + num2 * counter3))] =
     295             :                       &StaticCache::unwrap_cache_combined<
     296             :                           IntegralConstantValues..., IntegralConstant0,
     297             :                           IntegralConstant1, IntegralConstant2,
     298             :                           IntegralConstant3>;
     299             :                   ++counter0;
     300             :                 };
     301             :                 EXPAND_PACK_LEFT_TO_RIGHT(
     302             :                     helper0.template operator()<IntegralConstants0>());
     303             :                 ++counter1;
     304             :               };
     305             :               EXPAND_PACK_LEFT_TO_RIGHT(
     306             :                   helper1.template operator()<IntegralConstants1>());
     307             :               ++counter2;
     308             :             };
     309             :             EXPAND_PACK_LEFT_TO_RIGHT(
     310             :                 helper2.template operator()<IntegralConstants2>());
     311             :             ++counter3;
     312             :           };
     313             :           EXPAND_PACK_LEFT_TO_RIGHT(
     314             :               helper3.template operator()<IntegralConstants3>());
     315             :           return result;
     316             :         }();
     317             : 
     318             :     // The array `cache` holds pointers to member functions, so we
     319             :     // dereference the pointer and invoke it on `this`.
     320             :     return (
     321             :         this->*gsl::at(cache,
     322             :                        std::get<0>(parameter0) +
     323             :                            num0 * (std::get<0>(parameter1) +
     324             :                                    num1 * (std::get<0>(parameter2) +
     325             :                                            num2 * std::get<0>(parameter3)))))();
     326             :   }
     327             : 
     328             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     329             :             typename... IntegralConstants1, typename... IntegralConstants2,
     330             :             typename... IntegralConstants3, typename... IntegralConstants4,
     331             :             typename... Args>
     332           0 :   const T& unwrap_cache_combined(
     333             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     334             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     335             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     336             :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3,
     337             :       std::tuple<size_t, tmpl::list<IntegralConstants4...>> parameter4,
     338             :       const Args&... parameters) const {
     339             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     340             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     341             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     342             :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     343             :     constexpr size_t num4 = sizeof...(IntegralConstants4);
     344             :     constexpr size_t total_size = num0 * num1 * num2 * num3 * num4;
     345             :     // note that the act of assigning to the specified function pointer type
     346             :     // fixes the template arguments that need to be inferred.
     347             :     static const std::array<const T& (StaticCache::*)(Args...) const,
     348             :                             total_size>
     349             :         cache = []() {
     350             :           std::array<const T& (StaticCache::*)(Args...) const, total_size>
     351             :               result;
     352             :           size_t counter4 = 0;
     353             :           const auto helper4 = [&counter4,
     354             :                                 &result]<typename IntegralConstant4>() {
     355             :             size_t counter3 = 0;
     356             :             const auto helper3 = [&counter3, &counter4,
     357             :                                   &result]<typename IntegralConstant3>() {
     358             :               size_t counter2 = 0;
     359             :               const auto helper2 = [&counter2, &counter3, &counter4,
     360             :                                     &result]<typename IntegralConstant2>() {
     361             :                 size_t counter1 = 0;
     362             :                 const auto helper1 = [&counter1, &counter2, &counter3,
     363             :                                       &counter4,
     364             :                                       &result]<typename IntegralConstant1>() {
     365             :                   size_t counter0 = 0;
     366             :                   const auto helper0 = [&counter0, &counter1, &counter2,
     367             :                                         &counter3, &counter4,
     368             :                                         &result]<typename IntegralConstant0>() {
     369             :                     result[counter0 +
     370             :                            num0 *
     371             :                                (counter1 +
     372             :                                 num1 * (counter2 +
     373             :                                         num2 * (counter3 + num3 * counter4)))] =
     374             :                         &StaticCache::unwrap_cache_combined<
     375             :                             IntegralConstantValues..., IntegralConstant0,
     376             :                             IntegralConstant1, IntegralConstant2,
     377             :                             IntegralConstant3, IntegralConstant4>;
     378             :                     ++counter0;
     379             :                   };
     380             :                   EXPAND_PACK_LEFT_TO_RIGHT(
     381             :                       helper0.template operator()<IntegralConstants0>());
     382             :                   ++counter1;
     383             :                 };
     384             :                 EXPAND_PACK_LEFT_TO_RIGHT(
     385             :                     helper1.template operator()<IntegralConstants1>());
     386             :                 ++counter2;
     387             :               };
     388             :               EXPAND_PACK_LEFT_TO_RIGHT(
     389             :                   helper2.template operator()<IntegralConstants2>());
     390             :               ++counter3;
     391             :             };
     392             :             EXPAND_PACK_LEFT_TO_RIGHT(
     393             :                 helper3.template operator()<IntegralConstants3>());
     394             :             ++counter4;
     395             :           };
     396             :           EXPAND_PACK_LEFT_TO_RIGHT(
     397             :               helper4.template operator()<IntegralConstants4>());
     398             :           return result;
     399             :         }();
     400             : 
     401             :     // The array `cache` holds pointers to member functions, so we dereference
     402             :     // the pointer and invoke it on `this`.
     403             :     return (
     404             :         this->*gsl::at(
     405             :                    cache,
     406             :                    std::get<0>(parameter0) +
     407             :                        num0 *
     408             :                            (std::get<0>(parameter1) +
     409             :                             num1 * (std::get<0>(parameter2) +
     410             :                                     num2 * (std::get<0>(parameter3) +
     411             :                                             num3 * std::get<0>(parameter4))))))(
     412             :         parameters...);
     413             :   }
     414             : #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ > 10 && __GNUC__ < 14
     415             : #pragma GCC diagnostic pop
     416             : #endif
     417             : 
     418           0 :   const Generator generator_;
     419             : };
     420             : 
     421             : /// \ingroup UtilitiesGroup
     422             : /// Create a StaticCache, inferring the cached type from the generator.
     423             : template <typename... Ranges, typename Generator>
     424           1 : auto make_static_cache(Generator&& generator) {
     425             :   using CachedType = std::remove_cv_t<decltype(generator(
     426             :       std::declval<typename Ranges::value_type>()...))>;
     427             :   return StaticCache<std::remove_cv_t<Generator>, CachedType, Ranges...>(
     428             :       std::forward<Generator>(generator));
     429             : }

Generated by: LCOV version 1.14