openPMD-api
Attribute.hpp
1 /* Copyright 2017-2025 Fabian Koller, Axel Huebl, Franz Poeschel
2  *
3  * This file is part of openPMD-api.
4  *
5  * openPMD-api is free software: you can redistribute it and/or modify
6  * it under the terms of of either the GNU General Public License or
7  * the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * openPMD-api is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License and the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * and the GNU Lesser General Public License along with openPMD-api.
19  * If not, see <http://www.gnu.org/licenses/>.
20  */
21 #pragma once
22 
23 #include "openPMD/Datatype.hpp"
24 #include "openPMD/auxiliary/TypeTraits.hpp"
25 #include "openPMD/auxiliary/Variant.hpp"
26 
27 // comment to prevent clang-format from moving this #include up
28 // datatype macros may be included and un-included in other headers
29 #include "openPMD/DatatypeMacros.hpp"
30 
31 #include <algorithm>
32 #include <array>
33 #include <complex>
34 #include <cstdint>
35 #include <iterator>
36 #include <optional>
37 #include <stdexcept>
38 #include <string>
39 #include <type_traits>
40 #include <utility>
41 #include <variant>
42 #include <vector>
43 
44 namespace openPMD
45 {
46 // TODO This might have to be a Writable
47 // Reasoning - Flushes are expected to be done often.
48 // Attributes should not be written unless dirty.
49 // At the moment the dirty check is done at Attributable level,
50 // resulting in all of an Attributables Attributes being written to disk even if
51 // only one changes
56 #define OPENPMD_ENUMERATE_TYPES(type) , type
57 
58 class Attribute
59  : public auxiliary::Variant<Datatype OPENPMD_FOREACH_DATATYPE(
60  OPENPMD_ENUMERATE_TYPES)>
61 
62 #undef OPENPMD_ENUMERATE_TYPES
63 
64 {
65 public:
66  struct from_any_tag
67  {};
68  static constexpr from_any_tag from_any = from_any_tag{};
69 
80 #define OPENPMD_ATTRIBUTE_CONSTRUCTOR_FROM_VARIANT(TYPE) \
81  Attribute(TYPE val) : Variant(Variant::from_basic_type, std::move(val)) \
82  {}
83 
84  OPENPMD_FOREACH_DATATYPE(OPENPMD_ATTRIBUTE_CONSTRUCTOR_FROM_VARIANT)
85 
86 #undef OPENPMD_ATTRIBUTE_CONSTRUCTOR_FROM_VARIANT
87 
88  Attribute(from_any_tag, std::any val)
89  : Variant(Variant::from_any, std::move(val))
90  {}
91 
101  template <typename U>
102  U get() const;
103 
115  template <typename U>
116  std::optional<U> getOptional() const;
117 
130  template <typename U>
131  std::variant<U, std::runtime_error> getOrError() const;
132 
142  [[nodiscard]] std::variant<Attribute, std::runtime_error>
143  requireVector() const;
152  [[nodiscard]] std::variant<Attribute, std::runtime_error>
153  requireScalar() const;
154 };
155 
156 namespace detail
157 {
158  template <typename T, typename U>
159  auto doConvert(T const *pv) -> std::variant<U, std::runtime_error>
160  {
161  (void)pv;
162  if constexpr (std::is_convertible_v<T, U>)
163  {
164  return {static_cast<U>(*pv)};
165  }
166  else if constexpr (
167  std::is_same_v<T, std::string> && auxiliary::IsChar_v<U>)
168  {
169  if (pv->size() == 1)
170  {
171  return static_cast<U>(pv->at(0));
172  }
173  else
174  {
175  return {std::runtime_error(
176  "getCast: cast from string to char only "
177  "possible if string has length 1.")};
178  }
179  }
180  else if constexpr (
181  auxiliary::IsChar_v<T> && std::is_same_v<U, std::string>)
182  {
183  return std::string(1, *pv);
184  }
185  else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsVector_v<U>)
186  {
187  U res{};
188  res.reserve(pv->size());
189  if constexpr (
190  std::is_convertible_v<
191  typename T::value_type,
192  typename U::value_type>)
193  {
194  std::copy(pv->begin(), pv->end(), std::back_inserter(res));
195  return {res};
196  }
197  else
198  {
199  // try a dynamic conversion recursively
200  for (auto const &val : *pv)
201  {
202  auto conv = doConvert<
203  typename T::value_type,
204  typename U::value_type>(&val);
205  if (auto conv_val =
206  std::get_if<typename U::value_type>(&conv);
207  conv_val)
208  {
209  res.push_back(std::move(*conv_val));
210  }
211  else
212  {
213  auto exception = std::get<std::runtime_error>(conv);
214  return {std::runtime_error(
215  std::string(
216  "getCast: no vector cast possible, recursive "
217  "error: ") +
218  exception.what())};
219  }
220  }
221  return {res};
222  }
223  }
224  // conversion cast: array to vector
225  // if a backend reports a std::array<> for something where
226  // the frontend expects a vector
227  else if constexpr (auxiliary::IsArray_v<T> && auxiliary::IsVector_v<U>)
228  {
229  U res{};
230  res.reserve(pv->size());
231  if constexpr (
232  std::is_convertible_v<
233  typename T::value_type,
234  typename U::value_type>)
235  {
236  std::copy(pv->begin(), pv->end(), std::back_inserter(res));
237  return {res};
238  }
239  else
240  {
241  // try a dynamic conversion recursively
242  for (auto const &val : *pv)
243  {
244  auto conv = doConvert<
245  typename T::value_type,
246  typename U::value_type>(&val);
247  if (auto conv_val =
248  std::get_if<typename U::value_type>(&conv);
249  conv_val)
250  {
251  res.push_back(std::move(*conv_val));
252  }
253  else
254  {
255  auto exception = std::get<std::runtime_error>(conv);
256  return {std::runtime_error(
257  std::string(
258  "getCast: no array to vector conversion "
259  "possible, recursive error: ") +
260  exception.what())};
261  }
262  }
263  return {res};
264  }
265  }
266  // conversion cast: vector to array
267  // if a backend reports a std::vector<> for something where
268  // the frontend expects an array
269  else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsArray_v<U>)
270  {
271  U res{};
272  if constexpr (
273  std::is_convertible_v<
274  typename T::value_type,
275  typename U::value_type>)
276  {
277  if (res.size() != pv->size())
278  {
279  return std::runtime_error(
280  "getCast: no vector to array conversion possible "
281  "(wrong "
282  "requested array size).");
283  }
284  for (size_t i = 0; i < res.size(); ++i)
285  {
286  res[i] = static_cast<typename U::value_type>((*pv)[i]);
287  }
288  return {res};
289  }
290  else
291  {
292  // try a dynamic conversion recursively
293  for (size_t i = 0; i <= res.size(); ++i)
294  {
295  auto const &val = (*pv)[i];
296  auto conv = doConvert<
297  typename T::value_type,
298  typename U::value_type>(&val);
299  if (auto conv_val =
300  std::get_if<typename U::value_type>(&conv);
301  conv_val)
302  {
303  res[i] = std::move(*conv_val);
304  }
305  else
306  {
307  auto exception = std::get<std::runtime_error>(conv);
308  return {std::runtime_error(
309  std::string(
310  "getCast: no vector to array conversion "
311  "possible, recursive error: ") +
312  exception.what())};
313  }
314  }
315  return {res};
316  }
317  }
318  // conversion cast: turn a single value into a 1-element vector
319  else if constexpr (auxiliary::IsVector_v<U>)
320  {
321  U res{};
322  res.reserve(1);
323  if constexpr (std::is_convertible_v<T, typename U::value_type>)
324  {
325  res.push_back(static_cast<typename U::value_type>(*pv));
326  return {res};
327  }
328  else
329  {
330  // try a dynamic conversion recursively
331  auto conv = doConvert<T, typename U::value_type>(pv);
332  if (auto conv_val = std::get_if<typename U::value_type>(&conv);
333  conv_val)
334  {
335  res.push_back(std::move(*conv_val));
336  return {res};
337  }
338  else
339  {
340  auto exception = std::get<std::runtime_error>(conv);
341  return {std::runtime_error(
342  std::string(
343  "getCast: no scalar to vector conversion "
344  "possible, recursive error: ") +
345  exception.what())};
346  }
347  }
348  }
349  else
350  {
351  return {std::runtime_error("getCast: no cast possible.")};
352  }
353 #if defined(__INTEL_COMPILER)
354 /*
355  * ICPC has trouble with if constexpr, thinking that return statements are
356  * missing afterwards. Deactivate the warning.
357  * Note that putting a statement here will not help to fix this since it will
358  * then complain about unreachable code.
359  * https://community.intel.com/t5/Intel-C-Compiler/quot-if-constexpr-quot-and-quot-missing-return-statement-quot-in/td-p/1154551
360  */
361 #pragma warning(disable : 1011)
362  }
363 #pragma warning(default : 1011)
364 #else
365  }
366 #endif
367 
368  template <typename T, typename U>
369  auto doConvertOptional(T const *pv) -> std::optional<U>
370  {
371  auto eitherValueOrError = doConvert<T, U>(pv);
372  return std::visit(
373  [](auto &containedValue) -> std::optional<U> {
374  using Res = std::decay_t<decltype(containedValue)>;
375  if constexpr (std::is_same_v<Res, std::runtime_error>)
376  {
377  return std::nullopt;
378  }
379  else
380  {
381  return {std::move(containedValue)};
382  }
383  },
384  eitherValueOrError);
385  }
386 } // namespace detail
387 } // namespace openPMD
388 
389 #include "openPMD/UndefDatatypeMacros.hpp"
Definition: Attribute.hpp:64
std::variant< U, std::runtime_error > getOrError() const
Retrieve a stored specific Attribute and cast if convertible.
std::variant< Attribute, std::runtime_error > requireScalar() const
Force this attribute into a scalar type.
Definition: Attribute.cpp:230
std::variant< Attribute, std::runtime_error > requireVector() const
Force this attribute into a vector type.
Definition: Attribute.cpp:183
U get() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:112
std::optional< U > getOptional() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:132
Generic object to store a set of datatypes in without losing type safety.
Definition: Variant.hpp:40
Public definitions of openPMD-api.
Definition: Date.cpp:29
Definition: Attribute.hpp:67