Deferred.hpp
Go to the documentation of this file.
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 /// \file
5 /// Defines class Deferred and make function
6 
7 #pragma once
8 
9 #include <boost/make_shared.hpp>
10 #include <boost/shared_ptr.hpp>
11 #include <memory>
12 #include <tuple>
13 #include <type_traits>
14 #include <utility>
15 
16 #include "ErrorHandling/Error.hpp"
17 #include "Utilities/Gsl.hpp"
18 #include "Utilities/Requires.hpp"
19 #include "Utilities/TypeTraits.hpp"
20 
21 template <typename Rt, typename MakeConstReference = std::false_type>
22 class Deferred;
23 
24 namespace Deferred_detail {
25 template <typename T>
26 decltype(auto) retrieve_from_deferred(const T& t) noexcept {
27  return t;
28 }
29 
30 template <typename T, typename MakeConstReference>
31 decltype(auto) retrieve_from_deferred(
32  const Deferred<T, MakeConstReference>& t) noexcept {
33  return t.get();
34 }
35 
36 template <typename T>
37 struct remove_deferred {
38  using type = T;
39 };
40 
41 template <typename T, typename MakeConstReference>
42 struct remove_deferred<Deferred<T, MakeConstReference>> {
43  using type = T;
44 };
45 
46 template <typename T>
47 using remove_deferred_t = typename remove_deferred<T>::type;
48 
49 template <typename Rt>
50 class assoc_state {
51  public:
52  assoc_state() = default;
53  assoc_state(const assoc_state& /*rhs*/) = delete;
54  assoc_state& operator=(const assoc_state& /*rhs*/) = delete;
55  assoc_state(assoc_state&& /*rhs*/) = delete;
56  assoc_state& operator=(assoc_state&& /*rhs*/) = delete;
57  virtual const Rt& get() const noexcept = 0;
58  virtual Rt& mutate() noexcept = 0;
59  virtual void reset() noexcept = 0;
60  // clang-tidy: no non-const references
61  virtual void pack_unpack_lazy_function(PUP::er& p) noexcept = 0; // NOLINT
62  virtual bool evaluated() const noexcept = 0;
63  virtual boost::shared_ptr<assoc_state<Rt>> deep_copy() const noexcept = 0;
64  virtual ~assoc_state() = default;
65 };
66 
67 template <typename Rt>
68 class simple_assoc_state : public assoc_state<Rt> {
69  public:
70  explicit simple_assoc_state(Rt t) noexcept;
71 
72  const Rt& get() const noexcept override { return t_; }
73 
74  Rt& mutate() noexcept override { return t_; }
75 
76  void reset() noexcept override { ERROR("Cannot reset a simple_assoc_state"); }
77 
78  // clang-tidy: no non-const references
79  void pack_unpack_lazy_function(PUP::er& /*p*/) noexcept override { // NOLINT
80  ERROR("Cannot send a Deferred that's not a lazily evaluated function");
81  }
82 
83  bool evaluated() const noexcept override { return true; }
84 
85  boost::shared_ptr<assoc_state<Rt>> deep_copy() const noexcept override {
86  return deep_copy_impl();
87  }
88 
89  private:
90  template <typename T = Rt,
92  boost::shared_ptr<assoc_state<Rt>> deep_copy_impl() const noexcept {
93  return boost::make_shared<simple_assoc_state>(t_);
94  }
95 
96  template <typename T = Rt,
98  boost::shared_ptr<assoc_state<Rt>> deep_copy_impl() const noexcept {
99  ERROR(
100  "Cannot create a copy of a DataBox (e.g. using db::create_copy) that "
101  "holds a non-copyable simple item. The item type is '"
102  << pretty_type::get_name<T>() << "'.");
103  }
104 
105  Rt t_;
106 };
107 
108 template <typename Rt>
109 simple_assoc_state<Rt>::simple_assoc_state(Rt t) noexcept : t_(std::move(t)) {}
110 
111 template <typename Rt, typename Fp, typename... Args>
112 class deferred_assoc_state : public assoc_state<Rt> {
113  public:
114  explicit deferred_assoc_state(Fp f, Args... args) noexcept;
115  deferred_assoc_state(const deferred_assoc_state& /*rhs*/) = delete;
116  deferred_assoc_state& operator=(const deferred_assoc_state& /*rhs*/) = delete;
117  deferred_assoc_state(deferred_assoc_state&& /*rhs*/) = delete;
118  deferred_assoc_state& operator=(deferred_assoc_state&& /*rhs*/) = delete;
119  ~deferred_assoc_state() override = default;
120 
121  const Rt& get() const noexcept override {
122  if (not evaluated_) {
123  apply(std::make_index_sequence<sizeof...(Args)>{});
124  evaluated_ = true;
125  }
126  return t_;
127  }
128 
129  Rt& mutate() noexcept override { ERROR("Cannot mutate a computed Deferred"); }
130 
131  void reset() noexcept override { evaluated_ = false; }
132 
133  void update_args(std::decay_t<Args>... args) noexcept {
134  evaluated_ = false;
135  args_ = std::tuple<std::decay_t<Args>...>{std::move(args)...};
136  }
137 
138  // clang-tidy: no non-const references
139  void pack_unpack_lazy_function(PUP::er& p) noexcept override { // NOLINT
140  p | evaluated_;
141  if (evaluated_) {
142  p | t_;
143  }
144  }
145 
146  bool evaluated() const noexcept override { return evaluated_; }
147 
148  boost::shared_ptr<assoc_state<Rt>> deep_copy() const noexcept override {
149  ERROR(
150  "Have not yet implemented a deep_copy for deferred_assoc_state. It's "
151  "not at all clear if this is even possible because it is incorrect to "
152  "assume that the args_ have not changed.");
153  }
154 
155  private:
156  const Fp func_;
158  mutable bool evaluated_ = false;
159  mutable Rt t_;
160 
161  template <
162  size_t... Is,
163  Requires<((void)sizeof...(Is),
164  tt::is_callable_v<std::decay_t<Fp>,
165  remove_deferred_t<std::decay_t<Args>>...>)> =
166  nullptr>
167  void apply(std::integer_sequence<size_t, Is...> /*meta*/) const noexcept {
168  t_ = std::move(func_(retrieve_from_deferred(std::get<Is>(args_))...));
169  }
170 
171  template <
172  size_t... Is,
173  Requires<((void)sizeof...(Is),
176  remove_deferred_t<std::decay_t<Args>>...>)> = nullptr>
177  void apply(std::integer_sequence<size_t, Is...> /*meta*/) const noexcept {
178  func_(make_not_null(&t_), retrieve_from_deferred(std::get<Is>(args_))...);
179  }
180 };
181 
182 template <typename Rt, typename Fp, typename... Args>
183 deferred_assoc_state<Rt, Fp, Args...>::deferred_assoc_state(
184  Fp f, Args... args) noexcept
185  : func_(std::move(f)), args_(std::make_tuple(std::move(args)...)) {}
186 
187 // Specialization to handle functions that return a `const Rt&`. We treat the
188 // return value as pointer to the data we actually want to have visible to us
189 // when we retrieve the data. The reason for using a pointer is because we need
190 // to be able to rebind in case the memory address of `const Rt&` changes (for
191 // example, if we point to a `ConstGlobalCache` and we are migrated to a
192 // different node). Since lvalue references cannot be rebound, we store a
193 // pointer. The `get` function dereferences the pointer we store so that we have
194 // a const lvalue reference to work with when retrieving the data being pointed
195 // to. Dereferencing the pointer ensures that all functions that use the DataBox
196 // will be able to take a `const T& t` as input argument regardless of where the
197 // data is stored (in the DataBox or as a reference to somewhere else).
198 template <typename Rt, typename Fp, typename... Args>
199 class deferred_assoc_state<const Rt&, Fp, Args...> : public assoc_state<Rt> {
200  public:
201  explicit deferred_assoc_state(Fp f, Args... args) noexcept;
202  deferred_assoc_state(const deferred_assoc_state& /*rhs*/) = delete;
203  deferred_assoc_state& operator=(const deferred_assoc_state& /*rhs*/) = delete;
204  deferred_assoc_state(deferred_assoc_state&& /*rhs*/) = delete;
205  deferred_assoc_state& operator=(deferred_assoc_state&& /*rhs*/) = delete;
206  ~deferred_assoc_state() override = default;
207 
208  const Rt& get() const noexcept override {
209  if (not t_) {
210  apply(std::make_index_sequence<sizeof...(Args)>{});
211  }
212  return *t_;
213  }
214 
215  Rt& mutate() noexcept override { ERROR("Cannot mutate a compute tag."); }
216 
217  void reset() noexcept override { t_ = nullptr; }
218 
219  void update_args(std::decay_t<Args>... args) noexcept {
220  t_ = nullptr;
221  args_ = std::tuple<std::decay_t<Args>...>{std::move(args)...};
222  }
223 
224  // clang-tidy: no non-const references
225  void pack_unpack_lazy_function(PUP::er& /*p*/) noexcept override {} // NOLINT
226 
227  bool evaluated() const noexcept override { return t_ != nullptr; }
228 
229  boost::shared_ptr<assoc_state<Rt>> deep_copy() const noexcept override {
230  ERROR(
231  "Have not yet implemented a deep_copy for deferred_assoc_state. It's "
232  "not at all clear if this is even possible because it is incorrect to "
233  "assume that the args_ have not changed.");
234  }
235 
236  private:
237  const Fp func_;
239  mutable const Rt* t_ = nullptr;
240 
241  template <size_t... Is>
242  void apply(std::integer_sequence<size_t, Is...> /*meta*/) const noexcept {
243  t_ = &(func_(retrieve_from_deferred(std::get<Is>(args_))...));
244  }
245 };
246 
247 template <typename Rt, typename Fp, typename... Args>
248 deferred_assoc_state<const Rt&, Fp, Args...>::deferred_assoc_state(
249  Fp f, Args... args) noexcept
250  : func_(std::move(f)), args_(std::make_tuple(std::move(args)...)) {}
251 } // namespace Deferred_detail
252 
253 /*!
254  * \ingroup DataBoxGroup
255  * \brief Provides deferred or lazy evaluation of a function or function object,
256  * as well as efficient storage of an object that is mutable.
257  *
258  * The class is similar to a std::shared_future that is able to hold and allow
259  * mutation of objects. std::shared_future allows lazy evaluation of functions
260  * but does not allow mutation of stored objects. Since mutation is only defined
261  * for storage of objects, not for lazily evaluated functions, attempts to
262  * mutate a lazily evaluated function's data is an error.
263  *
264  * To construct a Deferred for lazy evaluation use the make_deferred() function.
265  *
266  * \example
267  * Construction of a Deferred with an object followed by mutation:
268  * \snippet Test_Deferred.cpp deferred_with_update
269  *
270  * \warning If passed a lazy function that returns a `const Rt& t` (a const
271  * lvalue reference) the underlying data stored is actually a pointer to `t`,
272  * which will be dereferenced upon retrieval. This could lead to a dangling
273  * pointer/reference if care isn't taken. The reason for this design is that:
274  * 1. We need to be able to retrieve data stored in the ConstGlobalCache from
275  * the DataBox. The way to do this is to have a pointer to the
276  * ConstGlobalCache inside the DataBox alongside compute items that return
277  * `const Rt&` to the ConstGlobalCache data.
278  * 2. Functions used in the DataBox shouldn't return references, they are
279  * compute tags and should return by value. If we find an actual use-case for
280  * compute tags returning `const Rt&` where referencing behavior is undesired
281  * then this design needs to be reconsidered. It is currently the least
282  * breaking way to implement referencing DataBox members that can point to
283  * any memory.
284  *
285  * @tparam Rt the type being stored
286  */
287 template <typename Rt, typename MakeConstReference>
288 class Deferred {
289  public:
291 
292  Deferred() = default;
293  template <typename Dummy = Rt,
295  explicit Deferred(Rt t)
296  : state_(boost::make_shared<Deferred_detail::simple_assoc_state<Rt>>(
297  std::move(t))) {}
298  Deferred(const Deferred&) = default;
299  Deferred& operator=(const Deferred&) = default;
300  Deferred(Deferred&&) = default;
301  Deferred& operator=(Deferred&&) = default;
302  ~Deferred() = default;
303 
304  constexpr const value_type& get() const noexcept { return state_->get(); }
305 
306  constexpr value_type& mutate() noexcept { return state_->mutate(); }
307 
308  // clang-tidy: no non-const references
309  void pack_unpack_lazy_function(PUP::er& p) noexcept { // NOLINT
310  state_->pack_unpack_lazy_function(p);
311  }
312 
313  bool evaluated() const noexcept { return state_->evaluated(); }
314 
315  void reset() noexcept { state_->reset(); }
316 
317  Deferred deep_copy() const noexcept { return Deferred{state_->deep_copy()}; }
318 
319  explicit Deferred(
320  boost::shared_ptr<Deferred_detail::assoc_state<tmpl::conditional_t<
321  MakeConstReference::value, const value_type&, value_type>>>&&
322  state) noexcept;
323 
324  private:
325  boost::shared_ptr<Deferred_detail::assoc_state<tmpl::conditional_t<
326  MakeConstReference::value, const value_type&, value_type>>>
327  state_{nullptr};
328 
329  // clang-tidy: redundant declaration
330  template <typename Rt1, typename Fp, typename... Args>
331  friend void update_deferred_args( // NOLINT
332  gsl::not_null<Deferred<Rt1>*> deferred, Fp /*f used for type deduction*/,
333  Args&&... args) noexcept;
334 
335  // clang-tidy: redundant declaration
336  template <typename Rt1, typename Fp, typename... Args>
337  friend void update_deferred_args( // NOLINT
338  gsl::not_null<Deferred<Rt1>*> deferred, Args&&... args) noexcept;
339 };
340 
341 template <typename Rt, typename MakeConstReference>
343  boost::shared_ptr<Deferred_detail::assoc_state<tmpl::conditional_t<
344  MakeConstReference::value, const value_type&, value_type>>>&&
345  state) noexcept
346  : state_(std::move(state)) {}
347 
348 /*!
349  * \ingroup DataBoxGroup
350  * \brief Create a deferred function call object
351  *
352  * If creating a Deferred with a function object the call operator of the
353  * function object must be marked `const` currently. Since the function object
354  * will only be evaluated once there currently seems to be no reason to allow
355  * mutating call operators.
356  *
357  * \example
358  * The examples below use the following functions:
359  * \snippet Test_Deferred.cpp functions_used
360  * To create a Deferred using a function object use:
361  * \snippet Test_Deferred.cpp make_deferred_with_function_object
362  * or using a regular function:
363  * \snippet Test_Deferred.cpp make_deferred_with_function
364  *
365  * It is also possible to pass Deferred objects to a deferred function call:
366  * \snippet Test_Deferred.cpp make_deferred_with_deferred_arg
367  * in which case the first function will be evaluated just before the second
368  * function is evaluated.
369  *
370  * In addition to functions that return by value, it is also possible to use
371  * functions that return by reference. The first argument of the function must
372  * then be a `gsl::not_null<Rt*>`, and can be mutated inside the function. The
373  * mutating functions are primarily useful if `Rt` performs heap allocations and
374  * is frequently recomputed in a manner where the heap allocation could be
375  * avoided.
376  *
377  * \tparam Rt the type of the object returned by the function
378  * @return Deferred object that will lazily evaluate the function
379  */
380 template <typename Rt, typename Fp, typename... Args>
381 Deferred<Rt> make_deferred(Fp f, Args&&... args) noexcept {
382  return Deferred<Rt>(boost::make_shared<Deferred_detail::deferred_assoc_state<
383  Rt, std::decay_t<Fp>, std::decay_t<Args>...>>(
384  f, std::forward<Args>(args)...));
385 }
386 
387 namespace Deferred_detail {
388 template <class Rt>
389 struct MakeDeferredForSubitemImpl {
390  template <typename Fp, typename... Args>
391  static Deferred<Rt> apply(Fp f, Args&&... args) noexcept {
392  return make_deferred<Rt>(f, std::forward<Args>(args)...);
393  }
394 };
395 
396 template <class Rt>
397 struct MakeDeferredForSubitemImpl<const Rt&> {
398  template <typename Fp, typename... Args>
399  static Deferred<Rt> apply(Fp f, Args&&... args) noexcept {
400  return Deferred<Rt>(
401  boost::make_shared<Deferred_detail::deferred_assoc_state<
402  const Rt&, std::decay_t<Fp>, std::decay_t<Args>...>>(
403  f, std::forward<Args>(args)...));
404  }
405 };
406 } // namespace Deferred_detail
407 
408 template <typename Rt, typename Fp, typename... Args>
409 auto make_deferred_for_subitem(Fp&& f, Args&&... args) noexcept {
411  std::forward<Fp>(f), std::forward<Args>(args)...);
412 }
413 
414 // @{
415 /*!
416  * \ingroup DataBoxGroup
417  * \brief Change the arguments to the Deferred function
418  *
419  * In order to make mutating Deferred functions really powerful, the `args` to
420  * them must be updated without destructing the held `Rt` object. The type of
421  * `Fp` (the invokable being lazily evaluated) as well as the types of the
422  * `std::decay_t<Args>...` must match their respective types at the time of
423  * creation of the Deferred object.
424  *
425  * \example
426  * You can avoid specifying the type of the function held by the Deferred class
427  * by passing the function as a second argument:
428  * \snippet Test_Deferred.cpp update_args_of_deferred_deduced_fp
429  *
430  * You can also specify the type of the function held by the Deferred explicitly
431  * as follows:
432  * \snippet Test_Deferred.cpp update_args_of_deferred_specified_fp
433  */
434 template <typename Rt, typename Fp, typename... Args>
436  Fp /*f used for type deduction*/,
437  Args&&... args) noexcept {
438  update_deferred_args<Rt, Fp>(deferred, std::forward<Args>(args)...);
439 }
440 
441 template <typename Rt, typename Fp, typename... Args>
443  Args&&... args) noexcept {
444  auto* ptr = dynamic_cast<Deferred_detail::deferred_assoc_state<
445  Rt, std::decay_t<Fp>, std::decay_t<Args>...>*>(deferred->state_.get());
446  if (ptr == nullptr) {
447  ERROR("Cannot cast the Deferred class to: "s
448  << (pretty_type::get_name<Deferred_detail::deferred_assoc_state<
449  Rt, std::decay_t<Fp>, std::decay_t<Args>...>>())
450  << " which means you are either passing in args of incorrect "
451  "types, that you are attempting to modify the args of a "
452  "Deferred that is not a lazily evaluated function, or that the "
453  "function type that the Deferred is expected to be holding is "
454  "incorrect."s);
455  }
456  ptr->update_args(std::forward<Args>(args)...);
457 }
458 // @}
void update_deferred_args(const gsl::not_null< Deferred< Rt > *> deferred, Fp, Args &&... args) noexcept
Change the arguments to the Deferred function.
Definition: Deferred.hpp:435
Definition: Strahlkorper.hpp:14
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:35
Definition: BoostMultiArray.hpp:11
void mutate(const gsl::not_null< DataBox< TagList > *> box, Invokable &&invokable, Args &&... args) noexcept
Allows changing the state of one or more non-computed elements in the DataBox.
Definition: DataBox.hpp:1099
std::string get_name()
Returns a string with the prettiest typename known for the type T.
Definition: PrettyType.hpp:674
Definition: Deferred.hpp:24
Defines the type alias Requires.
constexpr auto apply(F &&f, const DataBox< BoxTags > &box, Args &&... args)
Apply the function f with argument Tags TagsList from DataBox box
Definition: DataBox.hpp:1595
Deferred< Rt > make_deferred(Fp f, Args &&... args) noexcept
Create a deferred function call object.
Definition: Deferred.hpp:381
constexpr bool is_callable_v
Definition: TypeTraits.hpp:844
T make_shared(T... args)
Provides deferred or lazy evaluation of a function or function object, as well as efficient storage o...
Definition: Deferred.hpp:22
Defines functions and classes from the GSL.
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion, but it may be necessary to perform the conversion explicitly when type deduction is desired.
Definition: Gsl.hpp:863
typename Requires_detail::requires_impl< B >::template_error_type_failed_to_meet_requirements_on_template_parameters Requires
Express requirements on the template parameters of a function or class, replaces std::enable_if_t ...
Definition: Requires.hpp:67
Defines macro ERROR.
Defines type traits, some of which are future STL type_traits header.
Require a pointer to not be a nullptr
Definition: ConservativeFromPrimitive.hpp:12