Version: SMASH-3.4
key.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2024-2026
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #ifndef SRC_INCLUDE_SMASH_KEY_H_
11 #define SRC_INCLUDE_SMASH_KEY_H_
12 
13 #include <cassert>
14 #include <functional>
15 #include <optional>
16 #include <sstream>
17 #include <string>
18 #include <string_view>
19 #include <utility>
20 #include <vector>
21 
22 #include "logging.h"
23 #include "stringfunctions.h"
24 #include "traits.h"
25 
26 namespace smash {
27 
28 /**
29  * Descriptive alias for storing a SMASH version associated to keys metadata.
30  * At the moment simply a \c std::string .
31  */
32 using Version = std::string;
33 
34 /**
35  * Descriptive alias for storing keys metadata. At the moment this is only a
36  * list of versions.
37  */
38 using KeyMetadata = std::initializer_list<std::string_view>;
39 
40 /**
41  * Descriptive alias for storing key labels, i.e. the series of strings that
42  * identify a key in the input file from the main section. At the moment simply
43  * a \c std::vector<std::string_view> (key labels in the database are meant to
44  * be literals and this is why we can work with views here).
45  */
46 using KeyLabels = std::vector<std::string_view>;
47 
48 /**
49  * @brief New type to explicit distinguish between mandatory and optional keys.
50  */
51 enum class DefaultType {
52  /// %Default "type" for mandatory keys
53  Null,
54  /// Normal default with a value associated to it
55  Value,
56  /// %Default value which depends on other keys
57  Dependent
58 };
59 
60 namespace detail {
61 
62 /**
63  * @brief Class template to store Key traits outside the Key class, allowing for
64  * reuse both in the Key class itself and in helper implementation details.
65  *
66  * \tparam T The type of the key.
67  */
68 template <typename T>
69 struct KeyTraits {
70  /**
71  * @brief Descriptive alias for the key validator.
72  *
73  * @attention Since C++17 the \c noexcept specification of a function is part
74  * of the function signature but the \c std::function class template is not
75  * specialized on it as new C++23 class templates \c std::move_only_function
76  * or \c std::copyable_function are. Therefore it is not possible here to add
77  * and enforce the \c noexcept specification in the \c std::function template
78  * argument. Doing so would lead to a compilation error as the generic class
79  * template in the STL library is not implemented and it would be selected at
80  * instantiation time by the compiler.
81  */
82  using validator_type = std::function<bool(const T&)>;
83 };
84 
85 /**
86  * Function template to get a default trivial validator.
87  *
88  * @return A const reference to a functor that always returns \c true .
89  *
90  * @attention It might look unnecessary to have a function returning the functor
91  * and you might think that the functor as a constant global variable template
92  * would be enough. However, this would be in general wrong because this functor
93  * is used in the \c Key constructors which are used by the \c InputKeys class,
94  * that is a collection of static <tt>Key</tt>s. Hence, since initialization
95  * order of static/global objects in C++ is undefined, we need to do something
96  * else. We use therefore the "construct on first use idiom", making the functor
97  * a static object in a function scope. For more information, refer for example
98  * to <a
99  * href="https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use-members">ISO
100  * C++ FAQ</a>.
101  */
102 template <typename T>
104  static const typename KeyTraits<T>::validator_type always_true =
105  [](const T&) noexcept { return true; };
106  return always_true;
107 }
108 
109 } // namespace detail
110 
111 /**
112  * @brief Object to store a YAML input file key together with metadata
113  * associated to it.
114  *
115  * @note The class is designed such that all keys can be marked as deprecated
116  * and as removed. However, it is not possible to mark a key as removed
117  * without having deprecated it before. A workaround is to deprecate and
118  * remove it in the same version, i.e. specifying the same version twice
119  * at construction.
120  *
121  * @tparam default_type Type of the key value. This \b must be a plain type, by
122  * that meaning have no cv-qualifier and not being any among the
123  * following types: array, pointer, function, or a mix of them.
124  */
125 template <typename default_type>
126 class Key {
127  static_assert(!std::is_const_v<default_type>);
128  static_assert(!std::is_volatile_v<default_type>);
129  static_assert(!std::is_reference_v<default_type>);
130  static_assert(!std::is_pointer_v<default_type>);
131  static_assert(!std::is_array_v<default_type>);
132  static_assert(!std::is_function_v<default_type>);
133  static_assert(!std::is_member_object_pointer_v<default_type>);
134  static_assert(!std::is_member_function_pointer_v<default_type>);
135 
136  public:
137  /**
138  * \ingroup exception
139  * Thrown when too few or too many versions are passed to the constructor.
140  */
141  struct WrongNumberOfVersions : public std::runtime_error {
142  using std::runtime_error::runtime_error;
143  };
144 
145  /**
146  * \see detail::KeyTraits<T>::validator_type
147  */
150 
151  /**
152  * @brief Construct a new \c Key object without default value.
153  *
154  * @param[in] labels The label(s) identifying the key in the YAML input file.
155  * @param[in] versions A list of one, two or three version numbers identifying
156  * the versions in which the key has been introduced, deprecated and removed,
157  * respectively.
158  * @param[in] validator A functor that takes a default_type parameter and
159  * returns a bool variable.
160  *
161  * @throw WrongNumberOfVersions If \c versions has the wrong size.
162  */
163  explicit Key(const KeyLabels& labels, const KeyMetadata& versions,
164  validator_type validator)
165  : Key{labels, Default<default_type>{}, versions, validator} {}
166 
167  /**
168  * @brief Construct a new \c Key object with default value.
169  *
170  * @param[in] labels The label(s) identifying the key in the YAML input file.
171  * @param[in] value The key default value.
172  * @param[in] versions A list of one, two or three version numbers identifying
173  * the versions in which the key has been introduced, deprecated and removed,
174  * respectively.
175  * @param[in] validator A functor that takes a default_type parameter and
176  * returns a bool variable.
177  *
178  * @throw WrongNumberOfVersions If \c versions has the wrong size.
179  * @throw std::invalid_argument If \c validator(value) returns \c false .
180  */
181  Key(const KeyLabels& labels, default_type value, const KeyMetadata& versions,
182  validator_type validator)
183  : Key{labels, Default<default_type>{value}, versions, validator} {}
184 
185  /**
186  * @brief Construct a new \c Key object which is supposed to have a default
187  * value, which however depends on other keys and will remain unset.
188  *
189  * @param[in] labels The label(s) identifying the key in the YAML input file.
190  * @param[in] type_of_default The type of default value.
191  * @param[in] versions A list of one, two or three version numbers identifying
192  * the versions in which the key has been introduced, deprecated and removed,
193  * respectively.
194  * @param[in] validator A functor that takes a default_type parameter and
195  * returns a bool variable.
196  *
197  * @throw WrongNumberOfVersions If \c versions has the wrong size.
198  * @throw std::logic_error If \c type is not \c DefaultType::Dependent .
199  */
200  Key(const KeyLabels& labels, DefaultType type_of_default,
201  const KeyMetadata& versions, validator_type validator)
202  : Key{labels, Default<default_type>{type_of_default}, versions,
203  validator} {}
204 
205  /**
206  * @brief Let the clients of this class have access to the key type.
207  */
208  using type = default_type;
209 
210  /**
211  * @brief Get the default value of the key.
212  *
213  * @return A \c default_type variable.
214  *
215  * @throw std::bad_optional_access If the key has no default value.
216  */
217  default_type default_value() const { return default_.value(); }
218 
219  /**
220  * @brief Ask whether the default value depends on other other keys.
221  *
222  * @return \c true if this is the case,
223  * @return \c false if the default value is known or the key is mandatory.
224  */
225  bool has_dependent_default() const noexcept {
226  return default_.is_dependent();
227  }
228 
229  /**
230  * @brief Get the SMASH version in which the key has been introduced.
231  *
232  * @return A \c Version variable.
233  */
234  Version introduced_in() const noexcept { return introduced_in_; }
235 
236  /**
237  * @brief Get the SMASH version in which the key has been deprecated.
238  *
239  * @return A \c Version variable.
240  *
241  * @throw std::bad_optional_access If the key is not deprecated.
242  */
243  Version deprecated_in() const { return deprecated_in_.value(); }
244 
245  /**
246  * @brief Get the SMASH version in which the key has been removed.
247  *
248  * @return A \c Version variable.
249  *
250  * @throw std::bad_optional_access If the key is still allowed.
251  */
252  Version removed_in() const { return removed_in_.value(); }
253 
254  /**
255  * @brief Get whether the key is deprecated or not.
256  *
257  * @return \c true if the key is deprecated, \c false otherwise.
258  */
259  bool is_deprecated() const noexcept { return deprecated_in_.has_value(); }
260 
261  /**
262  * @brief Get whether the key is still allowed or not.
263  *
264  * @return \c true if the key is allowed, \c false otherwise.
265  */
266  bool is_allowed() const noexcept { return !removed_in_.has_value(); }
267 
268  /**
269  * @brief Get whether the given key value is valid.
270  *
271  * @note Since at the moment not-noexcept validators are accepted from this
272  * class, but we still want the \c noexcept specification for this method, we
273  * wrap the validation in a \c try block and give a non-fatal error if an
274  * exception is thrown by the validator. Note that the not-exceptional branch
275  * should always be run and, hence, no performance impact should occur.
276  *
277  * @param[in] value The value to be validated.
278  *
279  * @return \c true if the given value is valid,
280  * @return \c false otherwise.
281  */
282  bool validate(const default_type& value) const noexcept {
283  try {
284  return validator_(value);
285  } catch (...) {
286  logg[LogArea::Configuration::id].error(
287  "Validator of key " + static_cast<std::string>(*this) +
288  " threw an exception when validating key value.\nThis should not "
289  "happen. Considering value invalid.");
290  return false;
291  }
292  }
293 
294  /**
295  * @brief Check if given labels are the same as those of this object.
296  *
297  * @param[in] labels Given labels to be checked against.
298  *
299  * @return \c true if all labels match in the given order,
300  * @return \c false otherwise.
301  */
302  bool has_same_labels(const KeyLabels& labels) const noexcept {
303  return std::equal(std::begin(labels_), std::end(labels_),
304  std::begin(labels), std::end(labels));
305  }
306 
307  /**
308  * @brief Converts a Key to a \c std::string using all labels.
309  *
310  * @return \c std::string with labels concatenated with \c :␣ (colon-space)
311  * and quotes all around.
312  */
313  explicit operator std::string() const noexcept {
314  return smash::quote(smash::join(labels_, ": "));
315  }
316 
317  /**
318  * Build and return a YAML-formatted string in the compact form (using braces
319  * as single line).
320  *
321  * \param[in] value An \c std::optional value of the Key type. If a value is
322  * passed, this is added to the resulting string if its type is
323  * streamable using the \c << operator. If no value is passed and
324  * the key has a streamable default, this is used.
325  *
326  * @return \c std::string with labels formatted in a compact YAML format.
327  */
328  std::string as_yaml([[maybe_unused]] std::optional<default_type> value =
329  std::nullopt) const noexcept {
330  std::stringstream value_as_string{};
331  if constexpr (is_writable_to_stream_v<std::stringstream, default_type>) {
332  if (value) {
333  value_as_string << *value;
334  } else if (default_.type_ == DefaultType::Value) {
335  value_as_string << default_value();
336  }
337  } else {
338  value_as_string << "<not-streamable>";
339  }
340  return as_yaml(value_as_string.str());
341  }
342 
343  /**
344  * Overload of the method taking a string as value. This can be useful for non
345  * streamable types e.g. in tests.
346  *
347  * \note The passed \c value is not quoted and it is responsibility of the
348  * caller to properly quote it, if needed. This enables setting e.g.
349  * YAML maps as value.
350  *
351  * \see as_yaml
352  */
353  std::string as_yaml(std::string value) const noexcept {
354  std::stringstream result{};
355  result << "{" << smash::join(labels_, ": {") << ": " << value
356  << smash::join(std::vector<std::string>(labels_.size(), "}"), "");
357  return result.str();
358  }
359 
360  /**
361  * \brief Method to access the \c Key labels.
362  *
363  * \return A constant reference to the labels member for read-only access.
364  */
365  const KeyLabels& labels() const { return labels_; }
366 
367  private:
368  /**
369  * @brief Wrapper class around a type with the capability to both store the
370  * type of default and its value, if any exists. This class has 3 valid
371  * states:
372  *
373  * | State | `type_` | `value_` |
374  * | :---: | :-----: | :------: |
375  * | Required key | `DefaultType::Null` | `std::nullopt` |
376  * | %Default value | `DefaultType::Value` | ≠ `std::nullopt` |
377  * | %Key with dependent default | `DefaultType::Dependent` | `std::nullopt` |
378  *
379  * There is a constructor for each of the cases above.
380  *
381  * @tparam T The default value type.
382  *
383  * \note This is an implementation detail of the \c Key class and it is meant
384  * to be rigid in its usage. E.g., the constructor specifying a \c DefaultType
385  * is meant to only accept \c DefaultType::Dependent because this is the only
386  * way we want it to be used.
387  */
388  template <typename T>
389  class Default {
390  public:
391  /**
392  * @brief Construct a new \c Default object which denotes a mandatory value
393  * without a default. This is meant to be used for required keys.
394  */
395  Default() : type_{DefaultType::Null} {}
396  /**
397  * @brief Construct a new \c Default object storing its default value.
398  *
399  * @param in The default value to be stored
400  */
401  explicit Default(T in) : value_{std::move(in)} {}
402  /**
403  * @brief Construct a new \c Default object which has a value dependent on
404  * external information.
405  *
406  * @param type The type of default (it should be \c DefaultType::Dependent
407  * ).
408  *
409  * @throw std::logic_error if called with a type different from \c
410  * DefaultType::Dependent .
411  */
412  explicit Default(DefaultType type) : type_{type} {
413  if (type != DefaultType::Dependent) {
414  throw std::logic_error("Default constructor used with invalid type!");
415  }
416  }
417 
418  /**
419  * @brief Retrieve the default value stored in the object
420  *
421  * @return The default value stored
422  *
423  * @throw std::bad_optional_access If the object stores no default value.
424  */
425  T value() const { return value_.value(); }
426 
427  /**
428  * @brief Ask whether the default value depends on other external
429  * information.
430  *
431  * @return \c true if this is the case,
432  * @return \c false if the default value is known or none exists.
433  */
434  bool is_dependent() const noexcept {
435  return type_ == DefaultType::Dependent;
436  }
437 
438  private:
439  /// The type of default value
440  DefaultType type_ = DefaultType::Value;
441  /// The default value, if any
442  std::optional<T> value_ = std::nullopt;
443  // Make nested class friend of enclosing one. This class is anyhow an
444  // implementation detail and part of Key.
445  friend class Key<T>;
446  };
447 
448  /**
449  * @brief Private constructor of the Key object.
450  *
451  * This is meant to do the real construction, while the other public
452  * constructors just delegate to this one. This is possible because this
453  * constructor takes a \c Default argument and the other construct one to
454  * delegate construction.
455  *
456  * @see public constructor documentation for the parameters description.
457  */
458  Key(const KeyLabels& labels, Default<default_type> value,
459  const KeyMetadata& versions, validator_type validator)
460  : default_{std::move(value)},
461  labels_{labels.begin(), labels.end()},
462  validator_{std::move(validator)} {
463  /*
464  * The following switch statement is a compact way to initialize the
465  * three version member variables without repetition and lots of logic
466  * clauses. The versions variable can have 1, 2 or 3 entries. The use of
467  * the iterator is needed, since std::initializer_list has no access
468  * operator.
469  */
470  switch (auto it = versions.end(); versions.size()) {
471  case 3:
472  removed_in_ = *(--it);
473  [[fallthrough]];
474  case 2:
475  deprecated_in_ = *(--it);
476  [[fallthrough]];
477  case 1:
478  introduced_in_ = *(--it);
479  break;
480  default:
481  throw WrongNumberOfVersions(
482  "Key constructor needs one, two or three version numbers.");
483  }
484  /* Ensure validator_ is set, which is usually the case unless in scenarios
485  * that are particularly nasty to debug (e.g. calling this constructor from
486  * a static/global object using a static/global validator and hence hitting
487  * the undefined order of static initialisation).
488  *
489  * NOTE: Do NOT throw from here. For non-local static keys we prefer to
490  * assign the canonical default validator when an empty functor is provided;
491  * throwing during static initialization can cause termination or even
492  * undefined behavior.
493  */
494  if (!validator_) {
495  logg[LogArea::Configuration::id].error(
496  "Empty validator used at Key construction time.\nThis should not "
497  "happen. Using default validator instead.");
498  validator_ = detail::get_default_validator<default_type>();
499  }
500  if (default_.value_ && validator_(*(default_.value_)) == false) {
501  throw std::logic_error(
502  "Key " + static_cast<std::string>(*this) +
503  " has been declared with an invalid default value.");
504  }
505  }
506 
507  /// SMASH version in which the key has been introduced
508  Version introduced_in_{};
509  /// SMASH version in which the key has been deprecated, if any
510  std::optional<Version> deprecated_in_{};
511  /// SMASH version in which the key has been removed, if any
512  std::optional<Version> removed_in_{};
513  /// Key default value
515  /// The label(s) identifying the key in the YAML input file
516  KeyLabels labels_{};
517  /// The functor to validate key values
518  validator_type validator_{detail::get_default_validator<default_type>()};
519 };
520 
521 } // namespace smash
522 
523 #endif // SRC_INCLUDE_SMASH_KEY_H_
Wrapper class around a type with the capability to both store the type of default and its value,...
Definition: key.h:389
T value() const
Retrieve the default value stored in the object.
Definition: key.h:425
Default(T in)
Construct a new Default object storing its default value.
Definition: key.h:401
Default()
Construct a new Default object which denotes a mandatory value without a default.
Definition: key.h:395
Default(DefaultType type)
Construct a new Default object which has a value dependent on external information.
Definition: key.h:412
bool is_dependent() const noexcept
Ask whether the default value depends on other external information.
Definition: key.h:434
Object to store a YAML input file key together with metadata associated to it.
Definition: key.h:126
bool has_dependent_default() const noexcept
Ask whether the default value depends on other other keys.
Definition: key.h:225
bool is_allowed() const noexcept
Get whether the key is still allowed or not.
Definition: key.h:266
Key(const KeyLabels &labels, DefaultType type_of_default, const KeyMetadata &versions, validator_type validator)
Construct a new Key object which is supposed to have a default value, which however depends on other ...
Definition: key.h:200
bool has_same_labels(const KeyLabels &labels) const noexcept
Check if given labels are the same as those of this object.
Definition: key.h:302
std::string as_yaml(std::string value) const noexcept
Overload of the method taking a string as value.
Definition: key.h:353
Version deprecated_in() const
Get the SMASH version in which the key has been deprecated.
Definition: key.h:243
bool validate(const default_type &value) const noexcept
Get whether the given key value is valid.
Definition: key.h:282
Key(const KeyLabels &labels, Default< default_type > value, const KeyMetadata &versions, validator_type validator)
Private constructor of the Key object.
Definition: key.h:458
Version removed_in() const
Get the SMASH version in which the key has been removed.
Definition: key.h:252
Key(const KeyLabels &labels, const KeyMetadata &versions, validator_type validator)
Construct a new Key object without default value.
Definition: key.h:163
bool is_deprecated() const noexcept
Get whether the key is deprecated or not.
Definition: key.h:259
default_type default_value() const
Get the default value of the key.
Definition: key.h:217
const KeyLabels & labels() const
Method to access the Key labels.
Definition: key.h:365
std::string as_yaml([[maybe_unused]] std::optional< default_type > value=std::nullopt) const noexcept
Build and return a YAML-formatted string in the compact form (using braces as single line).
Definition: key.h:328
default_type type
Let the clients of this class have access to the key type.
Definition: key.h:208
Key(const KeyLabels &labels, default_type value, const KeyMetadata &versions, validator_type validator)
Construct a new Key object with default value.
Definition: key.h:181
typename detail::KeyTraits< default_type >::validator_type validator_type
Definition: key.h:149
Version introduced_in() const noexcept
Get the SMASH version in which the key has been introduced.
Definition: key.h:234
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > & logg
An array that stores all pre-configured Logger objects.
Definition: logging.h:245
const KeyTraits< T >::validator_type & get_default_validator() noexcept
Function template to get a default trivial validator.
Definition: key.h:103
Definition: action.h:24
DefaultType
New type to explicit distinguish between mandatory and optional keys.
Definition: key.h:51
@ Dependent
Default value which depends on other keys
@ Value
Normal default with a value associated to it.
@ Null
Default "type" for mandatory keys
std::vector< std::string_view > KeyLabels
Descriptive alias for storing key labels, i.e.
Definition: key.h:46
std::string quote(const std::string &s)
Add quotes around string.
std::initializer_list< std::string_view > KeyMetadata
Descriptive alias for storing keys metadata.
Definition: key.h:38
std::string join(const std::vector< std::string > &v, std::string_view delim)
Join strings using delimiter.
std::string Version
Descriptive alias for storing a SMASH version associated to keys metadata.
Definition: key.h:32
Thrown when too few or too many versions are passed to the constructor.
Definition: key.h:141
Class template to store Key traits outside the Key class, allowing for reuse both in the Key class it...
Definition: key.h:69
std::function< bool(const T &)> validator_type
Descriptive alias for the key validator.
Definition: key.h:82