TestHelpers.hpp
Go to the documentation of this file.
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 /// \file
5 /// Commonly used routines, functions and definitions shared amongst unit tests
6 
7 #pragma once
8 
10 
11 #include <algorithm>
12 #include <array>
13 #include <boost/algorithm/string/predicate.hpp>
14 #include <cstddef>
15 #include <iterator>
16 #include <memory>
17 #include <ostream>
18 #include <random>
19 #include <string>
20 #include <tuple>
21 
22 #include "DataStructures/DataBox/TagName.hpp"
25 #include "Parallel/Serialize.hpp"
26 #include "Utilities/ContainerHelpers.hpp"
30 #include "Utilities/Gsl.hpp"
31 #include "Utilities/MakeArray.hpp"
32 #include "Utilities/MakeString.hpp"
33 #include "Utilities/Requires.hpp"
34 #include "Utilities/StdArrayHelpers.hpp" // IWYU pragma: keep
35 #include "Utilities/TMPL.hpp"
36 #include "Utilities/Tuple.hpp"
37 #include "Utilities/TypeTraits.hpp"
38 #include "Utilities/TypeTraits/HasEquivalence.hpp"
39 
40 /*!
41  * \ingroup TestingFrameworkGroup
42  * \brief Serializes and deserializes an object `t` of type `T`
43  */
44 template <typename T>
46  static_assert(
48  "Cannot use serialize_and_deserialize if a class is not default "
49  "constructible.");
50  return deserialize<T>(serialize<T>(t).data());
51 }
52 
53 /*!
54  * \ingroup TestingFrameworkGroup
55  * \brief Serializes and deserializes an object `t` of type `T`
56  */
57 template <typename T>
59  const T& t) noexcept {
60  deserialize<T>(result, serialize<T>(t).data());
61 }
62 
63 /// \ingroup TestingFrameworkGroup
64 /// \brief Tests the serialization of comparable types
65 /// \example
66 /// \snippet Test_PupStlCpp11.cpp example_serialize_comparable
67 template <typename T>
68 void test_serialization(const T& t) {
69  static_assert(tt::has_equivalence_v<T>, "No operator== for T");
70  CHECK(t == serialize_and_deserialize(t));
71 }
72 
73 /// \ingroup TestingFrameworkGroup
74 /// \brief Test the serialization of a derived class via a base class pointer
75 /// \example
76 /// \snippet Test_PupStlCpp11.cpp example_serialize_derived
77 /// \tparam B the base class
78 /// \tparam D the derived class
79 /// \tparam Args deduced from `args`
80 /// \param args arguments passed to a constructor of the derived class
81 template <typename B, typename D, typename... Args>
82 void test_serialization_via_base(Args&&... args) {
83  static_assert(std::is_base_of_v<B, D>,
84  "passed input type is not derived from specified base");
85  static_assert(tt::has_equivalence_v<D>, "No operator== for derived class");
86  Parallel::register_derived_classes_with_charm<B>();
87  std::unique_ptr<B> base = std::make_unique<D>(args...);
89  CHECK_FALSE(nullptr == dynamic_cast<const D*>(pupped_base.get()));
90  const D derived(args...);
91  CHECK(derived == dynamic_cast<const D&>(*pupped_base));
92 }
93 
94 /// Test for copy semantics assuming operator== is implement correctly
95 template <typename T, Requires<tt::has_equivalence<T>::value> = nullptr>
96 void test_copy_semantics(const T& a) {
98  "Class is not copy assignable.");
100  "Class is not copy constructible.");
101  T b = a;
102  CHECK(b == a);
103  // clang-tidy: intentionally not a reference to force invocation of copy
104  // constructor
105  const T c(a); // NOLINT
106  CHECK(c == a);
107 #if defined(__clang__) && __clang_major__ > 6
108 #pragma GCC diagnostic push
109 #pragma GCC diagnostic ignored "-Wself-assign-overloaded"
110 #endif // defined(__clang__) && __clang_major__ > 6
111  // clang-tidy: self-assignment
112  b = b; // NOLINT
113 #if defined(__clang__) && __clang_major__ > 6
114 #pragma GCC diagnostic pop
115 #endif // defined(__clang__) && __clang_major__ > 6
116  CHECK(b == a);
117 }
118 
119 /// Test for move semantics assuming operator== is implemented correctly.
120 /// \requires `std::is_rvalue_reference<decltype(a)>::%value` is true.
121 /// If T is not default constructible, you pass additional
122 /// arguments that are used to construct a T.
123 template <typename T, Requires<tt::has_equivalence<T>::value> = nullptr,
124  typename... Args>
125 void test_move_semantics(T&& a, const T& comparison, Args&&... args) {
126  static_assert(std::is_rvalue_reference<decltype(a)>::value,
127  "Must move into test_move_semantics");
129  "Class is not nothrow move assignable.");
131  "Class is not nothrow move constructible.");
132  if (&a == &comparison or a != comparison) {
133  // We use ERROR instead of ASSERT (which we normally should be using) to
134  // guard against someone writing tests in Release mode where ASSERTs don't
135  // show up.
136  ERROR("'a' and 'comparison' must be distinct (but equal in value) objects");
137  }
138  T b{std::forward<Args>(args)...};
139  // clang-tidy: use std::forward instead of std::move
140  b = std::move(a); // NOLINT
141  {
142  INFO("Test move assignment");
143  CHECK(b == comparison);
144  }
145  T c(std::move(b));
146  {
147  INFO("Test move construction");
148  CHECK(c == comparison);
149  }
150 }
151 
152 /// Test for move semantics assuming operator== is implemented correctly.
153 /// \requires `std::is_rvalue_reference<decltype(a)>::%value` is true.
154 /// This version is included for use with the rare type that (likely
155 /// incorrectly) has not marked the move assignment and move construction
156 /// operators `noexcept`
157 template <typename T, Requires<tt::has_equivalence<T>::value> = nullptr,
158  typename... Args>
159 void test_throwing_move_semantics(T&& a, const T& comparison,
160  Args&&... args) noexcept {
161  static_assert(std::is_rvalue_reference<decltype(a)>::value,
162  "Must move into test_move_semantics");
163  static_assert(std::is_move_assignable<T>::value,
164  "Class is not nothrow move assignable.");
166  "Class is not nothrow move constructible.");
167  if (&a == &comparison or a != comparison) {
168  // We use ERROR instead of ASSERT (which we normally should be using) to
169  // guard against someone writing tests in Release mode where ASSERTs don't
170  // show up.
171  ERROR("'a' and 'comparison' must be distinct (but equal in value) objects");
172  }
173  T b(std::forward<Args>(args)...);
174  // clang-tidy: use std::forward instead of std::move
175  b = std::move(a); // NOLINT
176  CHECK(b == comparison);
177  T c(std::move(b));
178  CHECK(c == comparison);
179 }
180 
181 // Test for iterators
182 template <typename Container>
183 void test_iterators(Container& c) {
184  CHECK(std::distance(c.begin(), c.end()) ==
185  static_cast<decltype(std::distance(c.begin(), c.end()))>(c.size()));
186  CHECK(c.begin() == c.cbegin());
187  CHECK(c.end() == c.cend());
188 
189  const auto& const_c = c;
190  CHECK(std::distance(const_c.begin(), const_c.end()) ==
191  static_cast<decltype(std::distance(const_c.begin(), const_c.end()))>(
192  const_c.size()));
193  CHECK(const_c.begin() == const_c.cbegin());
194  CHECK(const_c.end() == const_c.cend());
195 }
196 
197 // Test for reverse iterators
198 template <typename Container>
199 void test_reverse_iterators(Container& c) {
200  CHECK(std::distance(c.rbegin(), c.rend()) ==
201  static_cast<decltype(std::distance(c.rbegin(), c.rend()))>(c.size()));
202 
203  CHECK(c.rbegin() == c.crbegin());
204  CHECK(c.rend() == c.crend());
205 
206  auto it = c.begin();
207  auto rit = c.rbegin();
208  auto end = c.end();
209  auto rend = c.rend();
210  auto cit = c.cbegin();
211  auto cend = c.cend();
212  auto crit = c.crbegin();
213  auto crend = c.crend();
214 
215  for (size_t i = 0; i < c.size(); i++) {
216  CHECK(*it == *(std::prev(rend, 1)));
217  CHECK(*rit == *(std::prev(end, 1)));
218  CHECK(*cit == *(std::prev(crend, 1)));
219  CHECK(*crit == *(std::prev(cend, 1)));
220  it++;
221  rit++;
222  rend--;
223  end--;
224  crit++;
225  cit++;
226  crend--;
227  cend--;
228  }
229 
230  const auto& const_c = c;
231  CHECK(std::distance(const_c.begin(), const_c.end()) ==
232  static_cast<decltype(std::distance(const_c.begin(), const_c.end()))>(
233  const_c.size()));
234  auto c_it = const_c.begin();
235  auto c_rit = const_c.rbegin();
236  auto c_end = const_c.end();
237  auto c_rend = const_c.rend();
238  for (size_t i = 0; i < c.size(); i++) {
239  CHECK(*c_it == *(std::prev(c_rend, 1)));
240  CHECK(*c_rit == *(std::prev(c_end, 1)));
241  c_it++;
242  c_rit++;
243  c_rend--;
244  c_end--;
245  }
246 }
247 
248 /*!
249  * \ingroup TestingFrameworkGroup
250  * \brief Function to test comparison operators. Pass values with
251  * less < greater.
252  */
253 template <typename T, typename U>
254 void check_cmp(const T& less, const U& greater) {
255  CHECK(less == less);
256  CHECK_FALSE(less == greater);
257  CHECK(less != greater);
258  CHECK_FALSE(less != less);
259  CHECK(less < greater);
260  CHECK_FALSE(greater < less);
261  CHECK(greater > less);
262  CHECK_FALSE(less > greater);
263  CHECK(less <= greater);
264  CHECK_FALSE(greater <= less);
265  CHECK(greater >= less);
266  CHECK_FALSE(less >= greater);
267  CHECK(less <= less);
268  CHECK_FALSE(less < less);
269  CHECK(less >= less);
270  CHECK_FALSE(less > less);
271 }
272 
273 /*!
274  * \ingroup TestingFrameworkGroup
275  * \brief Check a op b == c and also the op= version.
276  */
277 #define CHECK_OP(a, op, b, c) \
278  do { \
279  const auto& a_ = a; \
280  const auto& b_ = b; \
281  const auto& c_ = c; \
282  CHECK(a_ op b_ == c_); \
283  auto f = a_; \
284  CHECK((f op## = b_) == c_); \
285  CHECK(f == c_); \
286  } while (false)
287 
288 /*!
289  * \ingroup TestingFrameworkGroup
290  * \brief Calculates the derivative of an Invocable at a point x - represented
291  * by an array of doubles - in the domain of `map` with a sixth-order finite
292  * difference method.
293  *
294  * \details Intended for use with CoordinateMaps taking the domain {xi,eta,zeta}
295  * to the range {x,y,z}. This function calculates the derivative along the
296  * direction given by `direction` with a step size of `h`.
297  *
298  * \requires direction be between 0 and VolumeDim
299  */
300 template <typename Invocable, size_t VolumeDim>
301 std::result_of_t<const Invocable&(const std::array<double, VolumeDim>&)>
302 numerical_derivative(const Invocable& function,
304  const size_t direction, const double delta) noexcept {
305  ASSERT(0 <= direction and direction < VolumeDim,
306  "Trying to take derivative along axis " << direction);
307 
308  const auto dx = [direction, delta]() {
309  auto d = make_array<VolumeDim>(0.);
310  gsl::at(d, direction) = delta;
311  return d;
312  }();
313 
314  const std::array<double, VolumeDim> x_1ahead = x + dx;
315  const std::array<double, VolumeDim> x_2ahead = x_1ahead + dx;
316  const std::array<double, VolumeDim> x_3ahead = x_2ahead + dx;
317  const std::array<double, VolumeDim> x_1behind = x - dx;
318  const std::array<double, VolumeDim> x_2behind = x_1behind - dx;
319  const std::array<double, VolumeDim> x_3behind = x_2behind - dx;
320  return (1.0 / (60.0 * delta)) * function(x_3ahead) +
321  (-3.0 / (20.0 * delta)) * function(x_2ahead) +
322  (0.75 / delta) * function(x_1ahead) +
323  (-0.75 / delta) * function(x_1behind) +
324  (3.0 / (20.0 * delta)) * function(x_2behind) +
325  (-1.0 / (60.0 * delta)) * function(x_3behind);
326 }
327 
328 struct NonCopyable {
329  constexpr NonCopyable() = default;
330  constexpr NonCopyable(const NonCopyable&) = delete;
331  constexpr NonCopyable& operator=(const NonCopyable&) = delete;
332  constexpr NonCopyable(NonCopyable&&) = default;
333  NonCopyable& operator=(NonCopyable&&) = default;
334  ~NonCopyable() = default;
335 };
336 inline bool operator==(const NonCopyable& /*a*/,
337  const NonCopyable& /*b*/) noexcept {
338  return true;
339 }
340 inline bool operator!=(const NonCopyable& a, const NonCopyable& b) noexcept {
341  return not(a == b);
342 }
343 inline std::ostream& operator<<(std::ostream& os,
344  const NonCopyable& /*v*/) noexcept {
345  return os << "NC";
346 }
347 
349  public:
350  DoesNotThrow() noexcept = default;
351  DoesNotThrow(const DoesNotThrow&) noexcept = default;
352  DoesNotThrow& operator=(const DoesNotThrow&) noexcept = default;
353  DoesNotThrow(DoesNotThrow&&) noexcept = default;
354  DoesNotThrow& operator=(DoesNotThrow&&) noexcept = default;
355  ~DoesNotThrow() = default;
356 };
357 class DoesThrow {
358  public:
359  DoesThrow() noexcept(false);
360  DoesThrow(const DoesThrow&) noexcept(false);
361  DoesThrow& operator=(const DoesThrow&) noexcept(false);
362  DoesThrow(DoesThrow&&) noexcept(false);
363  DoesThrow& operator=(DoesThrow&&) noexcept(false);
364  ~DoesThrow() = default;
365 };
366 
367 /*!
368  * \ingroup TestingFrameworkGroup
369  * \brief Execute `func` and check that it throws an exception `expected`.
370  *
371  * \note The `.what()` strings of the thrown and `expected` exceptions are
372  * compared for a partial match only: the `expected.what()` string must be
373  * contained in (or equal to) the `.what()` string of the thrown exception.
374  */
375 template <typename Exception, typename ThrowingFunctor>
376 void test_throw_exception(const ThrowingFunctor& func,
377  const Exception& expected) {
378  try {
379  func();
380  INFO("Failed to throw any exception");
381  CHECK(false);
382  } catch (Exception& e) {
383  CAPTURE(e.what());
384  CAPTURE(expected.what());
385  CHECK(boost::contains(std::string(e.what()), std::string(expected.what())));
386  } catch (...) {
387  INFO("Failed to throw exception of type " +
388  pretty_type::get_name<Exception>());
389  CHECK(false);
390  }
391 }
392 
393 /// \cond
394 #define MAKE_GENERATOR_IMPL_FIRST_ARG(NAME, ...) NAME
395 #define MAKE_GENERATOR_IMPL_SECOND_ARG(NAME, SEED, ...) SEED
396 /// \endcond
397 
398 /// \ingroup TestingFrameworkGroup
399 /// \brief `MAKE_GENERATOR(NAME [, SEED])` declares a variable of name `NAME`
400 /// containing a generator of type `std::mt19937`.
401 ///
402 /// \details As the generator is made, `INFO` is called to make sure failed
403 /// tests provide seed information. `SEED` is chosen randomly if not supplied,
404 /// otherwise it must be a constant expression.
405 // What is going on here:
406 //
407 // If this is called as MAKE_GENERATOR(NAME):
408 // MAKE_GENERATOR_IMPL_FIRST_ARG(__VA_ARGS__, DUMMY_TOKEN)
409 // -> MAKE_GENERATOR_IMPL_FIRST_ARG(NAME, DUMMY_TOKEN)
410 // -> NAME
411 // MAKE_GENERATOR_IMPL_SECOND_ARG(
412 // __VA_ARGS__, std::random_device{}(), DUMMY_TOKEN)
413 // -> MAKE_GENERATOR_IMPL_SECOND_ARG(
414 // NAME, std::random_device{}(), DUMMY_TOKEN)
415 // -> std::random_device{}()
416 // So we create NAME with a random seed.
417 //
418 // In this case DUMMY_TOKEN is needed because the "..." in the IMPL
419 // macros has to match at least one thing.
420 //
421 // If this is called as MAKE_GENERATOR(NAME, SEED):
422 // MAKE_GENERATOR_IMPL_FIRST_ARG(__VA_ARGS__, DUMMY_TOKEN)
423 // -> MAKE_GENERATOR_IMPL_FIRST_ARG(NAME, SEED, DUMMY_TOKEN)
424 // -> NAME
425 // MAKE_GENERATOR_IMPL_SECOND_ARG(
426 // __VA_ARGS__, std::random_device{}(), DUMMY_TOKEN)
427 // -> MAKE_GENERATOR_IMPL_SECOND_ARG(
428 // NAME, SEED, std::random_device{}(), DUMMY_TOKEN)
429 // -> SEED
430 // So we create NAME with seed SEED.
431 //
432 // In this case the DUMMY_TOKEN is not necessary.
433 #define MAKE_GENERATOR(...) \
434  std::mt19937 MAKE_GENERATOR_IMPL_FIRST_ARG(__VA_ARGS__, DUMMY_TOKEN); \
435  /* Capture everything because we don't know what passed seed uses */ \
436  INFO("Seed is: " << [&]() noexcept { \
437  const auto MAKE_GENERATOR_seed = (MAKE_GENERATOR_IMPL_SECOND_ARG( \
438  __VA_ARGS__, std::random_device{}(), DUMMY_TOKEN)); \
439  MAKE_GENERATOR_IMPL_FIRST_ARG(__VA_ARGS__, DUMMY_TOKEN) \
440  .seed(MAKE_GENERATOR_seed); \
441  return MakeString{} << MAKE_GENERATOR_seed << " from " __FILE__ ":" \
442  << __LINE__; \
443  }())
444 
445 /*!
446  * \ingroup TestingFrameworkGroup
447  * \brief A wrapper around Catch's CHECK macro that checks approximate equality
448  * of each entry in each tag within a variables.
449  */
450 #define CHECK_VARIABLES_APPROX(a, b) \
451  do { \
452  INFO(__FILE__ ":" + std::to_string(__LINE__) + ": " #a " == " #b); \
453  check_variables_approx<std::common_type_t< \
454  std::decay_t<decltype(a)>, std::decay_t<decltype(b)>>>::apply(a, b); \
455  } while (false)
456 
457 /*!
458  * \ingroup TestingFrameworkGroup
459  * \brief Same as `CHECK_VARIABLES_APPROX`, but with a user-defined Approx.
460  * The third argument should be of type `Approx`.
461  */
462 #define CHECK_VARIABLES_CUSTOM_APPROX(a, b, appx) \
463  do { \
464  INFO(__FILE__ ":" + std::to_string(__LINE__) + ": " #a " == " #b); \
465  check_variables_approx<std::common_type_t< \
466  std::decay_t<decltype(a)>, std::decay_t<decltype(b)>>>::apply(a, b, \
467  appx); \
468  } while (false)
469 
470 template <typename Tag, typename TagList>
471 const auto& extract_value_for_variables_comparison(
472  const Variables<TagList>& input_vars, std::true_type /*meta*/) noexcept {
473  // only Scalars of spin-weighted quantities are currently allowed. If that
474  // changes, then this solution will no longer be workable.
475  return get(get<Tag>(input_vars)).data();
476 }
477 
478 template <typename Tag, typename TagList>
479 const auto& extract_value_for_variables_comparison(
480  const Variables<TagList>& input_vars, std::false_type /*meta*/) noexcept {
481  return get<Tag>(input_vars);
482 }
483 
484 template <typename T>
486 
487 template <typename TagList>
488 struct check_variables_approx<Variables<TagList>> {
489  // clang-tidy: non-const reference
490  static void apply(const Variables<TagList>& a, const Variables<TagList>& b,
491  Approx& appx = approx) { // NOLINT
492  tmpl::for_each<TagList>([&a, &b, &appx](auto x) {
493  using Tag = typename decltype(x)::type;
494  INFO(db::tag_name<Tag>());
495  const auto& a_val = extract_value_for_variables_comparison<Tag>(
497  const auto& b_val = extract_value_for_variables_comparison<Tag>(
499  CHECK_ITERABLE_CUSTOM_APPROX(a_val, b_val, appx);
500  });
501  }
502 };
503 
504 // an additional overload for SpinWeighted quantities that just forwards to
505 // checking on the wrapped `data()` for the SpinWeighted
506 template <typename T>
507 struct check_iterable_approx<T, Requires<is_any_spin_weighted_v<T>>> {
508  // clang-tidy: non-const reference
509  static void apply(const T& a, const T& b, Approx& appx = approx) { // NOLINT
510  check_iterable_approx<typename T::value_type>::apply(a.data(), b.data(),
511  appx);
512  }
513 };
gsl::at
constexpr T & at(std::array< T, N > &arr, Size index)
Retrieve a entry from a container, with checks in Debug mode that the index being retrieved is valid.
Definition: Gsl.hpp:125
RegisterDerivedClassesWithCharm.hpp
std::true_type
std::string
get
constexpr Tag::type & get(Variables< TagList > &v) noexcept
Return Tag::type pointing into the contiguous array.
Definition: Variables.hpp:660
test_serialization
void test_serialization(const T &t)
Tests the serialization of comparable types.
Definition: TestHelpers.hpp:68
std::rel_ops::operator!=
T operator!=(T... args)
Error.hpp
MakeArray.hpp
TestingFramework.hpp
iterator
NonCopyable
Definition: TestHelpers.hpp:328
random
std::is_default_constructible
tuple
Serialize.hpp
CHECK_ITERABLE_CUSTOM_APPROX
#define CHECK_ITERABLE_CUSTOM_APPROX(a, b, appx)
Same as CHECK_ITERABLE_APPROX with user-defined Approx. The third argument should be of type Approx.
Definition: TestingFramework.hpp:134
DoesThrow
Definition: TestHelpers.hpp:357
is_any_spin_weighted
Definition: SpinWeighted.hpp:232
std::is_nothrow_move_constructible
test_move_semantics
void test_move_semantics(T &&a, const T &comparison, Args &&... args)
Test for move semantics assuming operator== is implemented correctly.
Definition: TestHelpers.hpp:125
algorithm
std::is_rvalue_reference
test_copy_semantics
void test_copy_semantics(const T &a)
Test for copy semantics assuming operator== is implement correctly.
Definition: TestHelpers.hpp:96
numerical_derivative
std::result_of_t< const Invocable &(const std::array< double, VolumeDim > &)> numerical_derivative(const Invocable &function, const std::array< double, VolumeDim > &x, const size_t direction, const double delta) noexcept
Calculates the derivative of an Invocable at a point x - represented by an array of doubles - in the ...
Definition: TestHelpers.hpp:302
ERROR
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:37
check_cmp
void check_cmp(const T &less, const U &greater)
Function to test comparison operators. Pass values with less < greater.
Definition: TestHelpers.hpp:254
std::ostream
cstddef
Assert.hpp
array
Tuple.hpp
memory
serialize_and_deserialize
T serialize_and_deserialize(const T &t)
Serializes and deserializes an object t of type T
Definition: TestHelpers.hpp:45
std::rend
T rend(T... args)
std::is_copy_constructible
ASSERT
#define ASSERT(a, m)
Assert that an expression should be true.
Definition: Assert.hpp:49
test_throw_exception
void test_throw_exception(const ThrowingFunctor &func, const Exception &expected)
Execute func and check that it throws an exception expected.
Definition: TestHelpers.hpp:376
std::is_nothrow_move_assignable
Variables.hpp
DereferenceWrapper.hpp
Gsl.hpp
std::begin
T begin(T... args)
StdArrayHelpers.hpp
test_serialization_via_base
void test_serialization_via_base(Args &&... args)
Test the serialization of a derived class via a base class pointer.
Definition: TestHelpers.hpp:82
std::result_of_t
Requires.hpp
check_variables_approx
Definition: TestHelpers.hpp:485
DoesNotThrow
Definition: TestHelpers.hpp:348
std::end
T end(T... args)
std::is_copy_assignable
Requires
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
ostream
std::unique_ptr
test_throwing_move_semantics
void test_throwing_move_semantics(T &&a, const T &comparison, Args &&... args) noexcept
Test for move semantics assuming operator== is implemented correctly.
Definition: TestHelpers.hpp:159
TMPL.hpp
std::rbegin
T rbegin(T... args)
gsl::not_null
Require a pointer to not be a nullptr
Definition: ReadSpecThirdOrderPiecewisePolynomial.hpp:13
string