Skip to content

Commit 8c8d14f

Browse files
Add Attribute::getOptional<T>() and use to add some more dynamic datatype conversions at read time (#1278)
* Add and use Attribute::getOptional<T>() Useful for backends that might not report back the right numerical type * Unify code paths for both get() and getOptional() Note that this is slightly API breaking since the shared code was in a public header. All implementation details are now in the detail namespace. * CI fixes, clean up append_test
1 parent a83bc22 commit 8c8d14f

File tree

10 files changed

+240
-160
lines changed

10 files changed

+240
-160
lines changed

include/openPMD/backend/Attribute.hpp

Lines changed: 132 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
#include <complex>
3030
#include <cstdint>
3131
#include <iterator>
32+
#include <optional>
3233
#include <stdexcept>
3334
#include <string>
3435
#include <type_traits>
3536
#include <utility>
37+
#include <variant>
3638
#include <vector>
3739

3840
namespace openPMD
@@ -119,100 +121,117 @@ class Attribute
119121
*/
120122
template <typename U>
121123
U get() const;
124+
125+
/** Retrieve a stored specific Attribute and cast if convertible.
126+
* Like Attribute::get<>(), but returns an empty std::optional if no
127+
* conversion is possible instead of throwing an exception.
128+
*
129+
* @note This performs a static_cast and might introduce precision loss if
130+
* requested. Check dtype explicitly beforehand if needed.
131+
*
132+
* @tparam U Type of the object to be casted to.
133+
* @return Copy of the retrieved object, casted to type U.
134+
* An empty std::optional if no conversion is possible.
135+
*/
136+
template <typename U>
137+
std::optional<U> getOptional() const;
122138
};
123139

124-
template <typename T, typename U>
125-
auto doConvert(T *pv) -> U
140+
namespace detail
126141
{
127-
(void)pv;
128-
if constexpr (std::is_convertible_v<T, U>)
129-
{
130-
return static_cast<U>(*pv);
131-
}
132-
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsVector_v<U>)
142+
template <typename T, typename U>
143+
auto doConvert(T *pv) -> std::variant<U, std::runtime_error>
133144
{
134-
if constexpr (std::is_convertible_v<
135-
typename T::value_type,
136-
typename U::value_type>)
137-
{
138-
U res{};
139-
res.reserve(pv->size());
140-
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
141-
return res;
142-
}
143-
else
145+
(void)pv;
146+
if constexpr (std::is_convertible_v<T, U>)
144147
{
145-
throw std::runtime_error("getCast: no vector cast possible.");
148+
return static_cast<U>(*pv);
146149
}
147-
}
148-
// conversion cast: array to vector
149-
// if a backend reports a std::array<> for something where
150-
// the frontend expects a vector
151-
else if constexpr (auxiliary::IsArray_v<T> && auxiliary::IsVector_v<U>)
152-
{
153-
if constexpr (std::is_convertible_v<
154-
typename T::value_type,
155-
typename U::value_type>)
150+
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsVector_v<U>)
156151
{
157-
U res{};
158-
res.reserve(pv->size());
159-
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
160-
return res;
161-
}
162-
else
163-
{
164-
throw std::runtime_error(
165-
"getCast: no array to vector conversion possible.");
152+
if constexpr (std::is_convertible_v<
153+
typename T::value_type,
154+
typename U::value_type>)
155+
{
156+
U res{};
157+
res.reserve(pv->size());
158+
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
159+
return res;
160+
}
161+
else
162+
{
163+
return std::runtime_error("getCast: no vector cast possible.");
164+
}
166165
}
167-
}
168-
// conversion cast: vector to array
169-
// if a backend reports a std::vector<> for something where
170-
// the frontend expects an array
171-
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsArray_v<U>)
172-
{
173-
if constexpr (std::is_convertible_v<
174-
typename T::value_type,
175-
typename U::value_type>)
166+
// conversion cast: array to vector
167+
// if a backend reports a std::array<> for something where
168+
// the frontend expects a vector
169+
else if constexpr (auxiliary::IsArray_v<T> && auxiliary::IsVector_v<U>)
176170
{
177-
U res{};
178-
if (res.size() != pv->size())
171+
if constexpr (std::is_convertible_v<
172+
typename T::value_type,
173+
typename U::value_type>)
179174
{
180-
throw std::runtime_error(
181-
"getCast: no vector to array conversion possible (wrong "
182-
"requested array size).");
175+
U res{};
176+
res.reserve(pv->size());
177+
std::copy(pv->begin(), pv->end(), std::back_inserter(res));
178+
return res;
183179
}
184-
for (size_t i = 0; i < res.size(); ++i)
180+
else
185181
{
186-
res[i] = static_cast<typename U::value_type>((*pv)[i]);
182+
return std::runtime_error(
183+
"getCast: no array to vector conversion possible.");
187184
}
188-
return res;
189185
}
190-
else
186+
// conversion cast: vector to array
187+
// if a backend reports a std::vector<> for something where
188+
// the frontend expects an array
189+
else if constexpr (auxiliary::IsVector_v<T> && auxiliary::IsArray_v<U>)
191190
{
192-
throw std::runtime_error(
193-
"getCast: no vector to array conversion possible.");
191+
if constexpr (std::is_convertible_v<
192+
typename T::value_type,
193+
typename U::value_type>)
194+
{
195+
U res{};
196+
if (res.size() != pv->size())
197+
{
198+
return std::runtime_error(
199+
"getCast: no vector to array conversion possible "
200+
"(wrong "
201+
"requested array size).");
202+
}
203+
for (size_t i = 0; i < res.size(); ++i)
204+
{
205+
res[i] = static_cast<typename U::value_type>((*pv)[i]);
206+
}
207+
return res;
208+
}
209+
else
210+
{
211+
return std::runtime_error(
212+
"getCast: no vector to array conversion possible.");
213+
}
194214
}
195-
}
196-
// conversion cast: turn a single value into a 1-element vector
197-
else if constexpr (auxiliary::IsVector_v<U>)
198-
{
199-
if constexpr (std::is_convertible_v<T, typename U::value_type>)
215+
// conversion cast: turn a single value into a 1-element vector
216+
else if constexpr (auxiliary::IsVector_v<U>)
200217
{
201-
U res{};
202-
res.reserve(1);
203-
res.push_back(static_cast<typename U::value_type>(*pv));
204-
return res;
218+
if constexpr (std::is_convertible_v<T, typename U::value_type>)
219+
{
220+
U res{};
221+
res.reserve(1);
222+
res.push_back(static_cast<typename U::value_type>(*pv));
223+
return res;
224+
}
225+
else
226+
{
227+
return std::runtime_error(
228+
"getCast: no scalar to vector conversion possible.");
229+
}
205230
}
206231
else
207232
{
208-
throw std::runtime_error(
209-
"getCast: no scalar to vector conversion possible.");
233+
return std::runtime_error("getCast: no cast possible.");
210234
}
211-
}
212-
else
213-
{
214-
throw std::runtime_error("getCast: no cast possible.");
215-
}
216235
#if defined(__INTEL_COMPILER)
217236
/*
218237
* ICPC has trouble with if constexpr, thinking that return statements are
@@ -222,35 +241,58 @@ auto doConvert(T *pv) -> U
222241
* https://community.intel.com/t5/Intel-C-Compiler/quot-if-constexpr-quot-and-quot-missing-return-statement-quot-in/td-p/1154551
223242
*/
224243
#pragma warning(disable : 1011)
225-
}
244+
}
226245
#pragma warning(default : 1011)
227246
#else
228-
}
247+
}
229248
#endif
249+
} // namespace detail
230250

231-
/** Retrieve a stored specific Attribute and cast if convertible.
232-
*
233-
* @throw std::runtime_error if stored object is not static castable to U.
234-
* @tparam U Type of the object to be casted to.
235-
* @return Copy of the retrieved object, casted to type U.
236-
*/
237251
template <typename U>
238-
inline U getCast(Attribute const &a)
252+
U Attribute::get() const
239253
{
240-
auto v = a.getResource();
241-
254+
auto eitherValueOrError = std::visit(
255+
[](auto &&containedValue) -> std::variant<U, std::runtime_error> {
256+
using containedType = std::decay_t<decltype(containedValue)>;
257+
return detail::doConvert<containedType, U>(&containedValue);
258+
},
259+
Variant::getResource());
242260
return std::visit(
243261
[](auto &&containedValue) -> U {
244-
using containedType = std::decay_t<decltype(containedValue)>;
245-
return doConvert<containedType, U>(&containedValue);
262+
using T = std::decay_t<decltype(containedValue)>;
263+
if constexpr (std::is_same_v<T, std::runtime_error>)
264+
{
265+
throw std::move(containedValue);
266+
}
267+
else
268+
{
269+
return std::move(containedValue);
270+
}
246271
},
247-
v);
272+
std::move(eitherValueOrError));
248273
}
249274

250275
template <typename U>
251-
U Attribute::get() const
276+
std::optional<U> Attribute::getOptional() const
252277
{
253-
return getCast<U>(Variant::getResource());
278+
auto eitherValueOrError = std::visit(
279+
[](auto &&containedValue) -> std::variant<U, std::runtime_error> {
280+
using containedType = std::decay_t<decltype(containedValue)>;
281+
return detail::doConvert<containedType, U>(&containedValue);
282+
},
283+
Variant::getResource());
284+
return std::visit(
285+
[](auto &&containedValue) -> std::optional<U> {
286+
using T = std::decay_t<decltype(containedValue)>;
287+
if constexpr (std::is_same_v<T, std::runtime_error>)
288+
{
289+
return std::nullopt;
290+
}
291+
else
292+
{
293+
return {std::move(containedValue)};
294+
}
295+
},
296+
std::move(eitherValueOrError));
254297
}
255-
256298
} // namespace openPMD

include/openPMD/backend/BaseRecord.hpp

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -317,24 +317,10 @@ inline void BaseRecord<T_elem>::readBase()
317317
aRead.name = "unitDimension";
318318
this->IOHandler()->enqueue(IOTask(this, aRead));
319319
this->IOHandler()->flush(internal::defaultFlushParams);
320-
if (*aRead.dtype == DT::ARR_DBL_7)
321-
this->setAttribute(
322-
"unitDimension",
323-
Attribute(*aRead.resource).template get<std::array<double, 7> >());
324-
else if (*aRead.dtype == DT::VEC_DOUBLE)
325-
{
326-
auto vec =
327-
Attribute(*aRead.resource).template get<std::vector<double> >();
328-
if (vec.size() == 7)
329-
{
330-
std::array<double, 7> arr;
331-
std::copy(vec.begin(), vec.end(), arr.begin());
332-
this->setAttribute("unitDimension", arr);
333-
}
334-
else
335-
throw std::runtime_error(
336-
"Unexpected Attribute datatype for 'unitDimension'");
337-
}
320+
if (auto val =
321+
Attribute(*aRead.resource).getOptional<std::array<double, 7> >();
322+
val.has_value())
323+
this->setAttribute("unitDimension", val.value());
338324
else
339325
throw std::runtime_error(
340326
"Unexpected Attribute datatype for 'unitDimension'");
@@ -344,10 +330,14 @@ inline void BaseRecord<T_elem>::readBase()
344330
this->IOHandler()->flush(internal::defaultFlushParams);
345331
if (*aRead.dtype == DT::FLOAT)
346332
this->setAttribute(
347-
"timeOffset", Attribute(*aRead.resource).template get<float>());
333+
"timeOffset", Attribute(*aRead.resource).get<float>());
348334
else if (*aRead.dtype == DT::DOUBLE)
349335
this->setAttribute(
350-
"timeOffset", Attribute(*aRead.resource).template get<double>());
336+
"timeOffset", Attribute(*aRead.resource).get<double>());
337+
// conversion cast if a backend reports an integer type
338+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
339+
val.has_value())
340+
this->setAttribute("timeOffset", val.value());
351341
else
352342
throw std::runtime_error(
353343
"Unexpected Attribute datatype for 'timeOffset'");

src/Iteration.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ void Iteration::read_impl(std::string const &groupPath)
425425
setDt(Attribute(*aRead.resource).get<double>());
426426
else if (*aRead.dtype == DT::LONG_DOUBLE)
427427
setDt(Attribute(*aRead.resource).get<long double>());
428+
// conversion cast if a backend reports an integer type
429+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
430+
val.has_value())
431+
setDt(val.value());
428432
else
429433
throw std::runtime_error("Unexpected Attribute datatype for 'dt'");
430434

@@ -437,14 +441,19 @@ void Iteration::read_impl(std::string const &groupPath)
437441
setTime(Attribute(*aRead.resource).get<double>());
438442
else if (*aRead.dtype == DT::LONG_DOUBLE)
439443
setTime(Attribute(*aRead.resource).get<long double>());
444+
// conversion cast if a backend reports an integer type
445+
else if (auto val = Attribute(*aRead.resource).getOptional<double>();
446+
val.has_value())
447+
setTime(val.value());
440448
else
441449
throw std::runtime_error("Unexpected Attribute datatype for 'time'");
442450

443451
aRead.name = "timeUnitSI";
444452
IOHandler()->enqueue(IOTask(this, aRead));
445453
IOHandler()->flush(internal::defaultFlushParams);
446-
if (*aRead.dtype == DT::DOUBLE)
447-
setTimeUnitSI(Attribute(*aRead.resource).get<double>());
454+
if (auto val = Attribute(*aRead.resource).getOptional<double>();
455+
val.has_value())
456+
setTimeUnitSI(val.value());
448457
else
449458
throw std::runtime_error(
450459
"Unexpected Attribute datatype for 'timeUnitSI'");

src/Mesh.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,25 +341,30 @@ void Mesh::read()
341341
else if (
342342
*aRead.dtype == DT::VEC_LONG_DOUBLE || *aRead.dtype == DT::LONG_DOUBLE)
343343
setGridSpacing(a.get<std::vector<long double> >());
344+
// conversion cast if a backend reports an integer type
345+
else if (auto val = a.getOptional<std::vector<double> >(); val.has_value())
346+
setGridSpacing(val.value());
344347
else
345348
throw std::runtime_error(
346349
"Unexpected Attribute datatype for 'gridSpacing'");
347350

348351
aRead.name = "gridGlobalOffset";
349352
IOHandler()->enqueue(IOTask(this, aRead));
350353
IOHandler()->flush(internal::defaultFlushParams);
351-
if (*aRead.dtype == DT::VEC_DOUBLE || *aRead.dtype == DT::DOUBLE)
352-
setGridGlobalOffset(
353-
Attribute(*aRead.resource).get<std::vector<double> >());
354+
if (auto val =
355+
Attribute(*aRead.resource).getOptional<std::vector<double> >();
356+
val.has_value())
357+
setGridGlobalOffset(val.value());
354358
else
355359
throw std::runtime_error(
356360
"Unexpected Attribute datatype for 'gridGlobalOffset'");
357361

358362
aRead.name = "gridUnitSI";
359363
IOHandler()->enqueue(IOTask(this, aRead));
360364
IOHandler()->flush(internal::defaultFlushParams);
361-
if (*aRead.dtype == DT::DOUBLE)
362-
setGridUnitSI(Attribute(*aRead.resource).get<double>());
365+
if (auto val = Attribute(*aRead.resource).getOptional<double>();
366+
val.has_value())
367+
setGridUnitSI(val.value());
363368
else
364369
throw std::runtime_error(
365370
"Unexpected Attribute datatype for 'gridUnitSI'");

0 commit comments

Comments
 (0)