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 
118 private:
119  template <typename U>
120  std::variant<U, std::runtime_error> get_impl() const;
121 };
122 
123 namespace detail
124 {
125  template <typename T, typename U>
126  auto doConvert(T const *pv) -> std::variant<U, std::runtime_error>
127  {
128  (void)pv;
129  if constexpr (std::is_convertible_v<T, U>)
130  {
131  return {static_cast<U>(*pv)};
132  }
133  else if constexpr (
134  std::is_same_v<T, std::string> && auxiliary::IsChar_v<U>)
135  {
136  if (pv->size() == 1)
137  {
138  return static_cast<U>(pv->at(0));
139  }
140  else
141  {
142  return {std::runtime_error(
143  "getCast: cast from string to char only "
144  "possible if string has length 1.")};
145  }
146  }
147  else if constexpr (
148  auxiliary::IsChar_v<T> && std::is_same_v<U, std::string>)
149  {
150  return std::string(1, *pv);
151  }
152  else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsVector_v<U>)
153  {
154  U res{};
155  res.reserve(pv->size());
156  if constexpr (std::is_convertible_v<
157  typename T::value_type,
158  typename U::value_type>)
159  {
160  std::copy(pv->begin(), pv->end(), std::back_inserter(res));
161  return {res};
162  }
163  else
164  {
165  // try a dynamic conversion recursively
166  for (auto const &val : *pv)
167  {
168  auto conv = doConvert<
169  typename T::value_type,
170  typename U::value_type>(&val);
171  if (auto conv_val =
172  std::get_if<typename U::value_type>(&conv);
173  conv_val)
174  {
175  res.push_back(std::move(*conv_val));
176  }
177  else
178  {
179  auto exception = std::get<std::runtime_error>(conv);
180  return {std::runtime_error(
181  std::string(
182  "getCast: no vector cast possible, recursive "
183  "error: ") +
184  exception.what())};
185  }
186  }
187  return {res};
188  }
189  }
190  // conversion cast: array to vector
191  // if a backend reports a std::array<> for something where
192  // the frontend expects a vector
193  else if constexpr (auxiliary::IsArray_v<T> && auxiliary::IsVector_v<U>)
194  {
195  U res{};
196  res.reserve(pv->size());
197  if constexpr (std::is_convertible_v<
198  typename T::value_type,
199  typename U::value_type>)
200  {
201  std::copy(pv->begin(), pv->end(), std::back_inserter(res));
202  return {res};
203  }
204  else
205  {
206  // try a dynamic conversion recursively
207  for (auto const &val : *pv)
208  {
209  auto conv = doConvert<
210  typename T::value_type,
211  typename U::value_type>(&val);
212  if (auto conv_val =
213  std::get_if<typename U::value_type>(&conv);
214  conv_val)
215  {
216  res.push_back(std::move(*conv_val));
217  }
218  else
219  {
220  auto exception = std::get<std::runtime_error>(conv);
221  return {std::runtime_error(
222  std::string(
223  "getCast: no array to vector conversion "
224  "possible, recursive error: ") +
225  exception.what())};
226  }
227  }
228  return {res};
229  }
230  }
231  // conversion cast: vector to array
232  // if a backend reports a std::vector<> for something where
233  // the frontend expects an array
234  else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsArray_v<U>)
235  {
236  U res{};
237  if constexpr (std::is_convertible_v<
238  typename T::value_type,
239  typename U::value_type>)
240  {
241  if (res.size() != pv->size())
242  {
243  return std::runtime_error(
244  "getCast: no vector to array conversion possible "
245  "(wrong "
246  "requested array size).");
247  }
248  for (size_t i = 0; i < res.size(); ++i)
249  {
250  res[i] = static_cast<typename U::value_type>((*pv)[i]);
251  }
252  return {res};
253  }
254  else
255  {
256  // try a dynamic conversion recursively
257  for (size_t i = 0; i <= res.size(); ++i)
258  {
259  auto const &val = (*pv)[i];
260  auto conv = doConvert<
261  typename T::value_type,
262  typename U::value_type>(&val);
263  if (auto conv_val =
264  std::get_if<typename U::value_type>(&conv);
265  conv_val)
266  {
267  res[i] = std::move(*conv_val);
268  }
269  else
270  {
271  auto exception = std::get<std::runtime_error>(conv);
272  return {std::runtime_error(
273  std::string(
274  "getCast: no vector to array conversion "
275  "possible, recursive error: ") +
276  exception.what())};
277  }
278  }
279  return {res};
280  }
281  }
282  // conversion cast: turn a single value into a 1-element vector
283  else if constexpr (auxiliary::IsVector_v<U>)
284  {
285  U res{};
286  res.reserve(1);
287  if constexpr (std::is_convertible_v<T, typename U::value_type>)
288  {
289  res.push_back(static_cast<typename U::value_type>(*pv));
290  return {res};
291  }
292  else
293  {
294  // try a dynamic conversion recursively
295  auto conv = doConvert<T, typename U::value_type>(pv);
296  if (auto conv_val = std::get_if<typename U::value_type>(&conv);
297  conv_val)
298  {
299  res.push_back(std::move(*conv_val));
300  return {res};
301  }
302  else
303  {
304  auto exception = std::get<std::runtime_error>(conv);
305  return {std::runtime_error(
306  std::string(
307  "getCast: no scalar to vector conversion "
308  "possible, recursive error: ") +
309  exception.what())};
310  }
311  }
312  }
313  else
314  {
315  return {std::runtime_error("getCast: no cast possible.")};
316  }
317 #if defined(__INTEL_COMPILER)
318 /*
319  * ICPC has trouble with if constexpr, thinking that return statements are
320  * missing afterwards. Deactivate the warning.
321  * Note that putting a statement here will not help to fix this since it will
322  * then complain about unreachable code.
323  * https://community.intel.com/t5/Intel-C-Compiler/quot-if-constexpr-quot-and-quot-missing-return-statement-quot-in/td-p/1154551
324  */
325 #pragma warning(disable : 1011)
326  }
327 #pragma warning(default : 1011)
328 #else
329  }
330 #endif
331 
332  template <typename T, typename U>
333  auto doConvertOptional(T const *pv) -> std::optional<U>
334  {
335  auto eitherValueOrError = doConvert<T, U>(pv);
336  return std::visit(
337  [](auto &containedValue) -> std::optional<U> {
338  using Res = std::decay_t<decltype(containedValue)>;
339  if constexpr (std::is_same_v<Res, std::runtime_error>)
340  {
341  return std::nullopt;
342  }
343  else
344  {
345  return {std::move(containedValue)};
346  }
347  },
348  eitherValueOrError);
349  }
350 } // namespace detail
351 } // namespace openPMD
352 
353 #include "openPMD/UndefDatatypeMacros.hpp"
Definition: Attribute.hpp:64
U get() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:105
std::optional< U > getOptional() const
Retrieve a stored specific Attribute and cast if convertible.
Definition: Attribute.cpp:125
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