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