io.h Source File

CPP API: io.h Source File
io.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Daniel Abele, Wadim Koslow
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 
21 #ifndef MIO_IO_IO_H
22 #define MIO_IO_IO_H
23 
27 #include "boost/outcome/result.hpp"
28 #include "boost/outcome/try.hpp"
29 #include "boost/optional.hpp"
30 
31 #include <bitset>
32 #include <concepts>
33 #include <cstddef>
34 #include <string>
35 #include <tuple>
36 #include <iostream>
37 #include <type_traits>
38 #include <utility>
39 
40 namespace mio
41 {
42 
48 enum class StatusCode
49 {
50  OK = 0, //must be 0 because std::error_code stores ints and expects 0 to be no error
51  UnknownError = 1, //errors must be non-zero
52  OutOfRange,
58 };
59 
65 enum IOFlags
66 {
70  IOF_None = 0,
71 
77 
82  IOF_OmitValues = 1 << 1,
83 
89 };
90 
91 } // namespace mio
92 
93 namespace std
94 {
95 
96 //make enum compatible with std::error_code
97 template <>
98 struct is_error_code_enum<mio::StatusCode> : true_type {
99 };
100 
101 } // namespace std
102 
103 namespace mio
104 {
105 
106 namespace details
107 {
111 class StatusCodeCategory : public std::error_category
112 {
113 public:
117  virtual const char* name() const noexcept override final
118  {
119  return "StatusCode";
120  }
121 
125  virtual std::string message(int c) const override final
126  {
127  switch (static_cast<StatusCode>(c)) {
128  case StatusCode::OK:
129  return "No error";
131  return "Invalid range";
133  return "Invalid value";
135  return "Invalid file format";
137  return "Key not found";
139  return "Invalid type";
141  return "File not found";
142  default:
143  return "Unknown Error";
144  }
145  }
146 
150  virtual std::error_condition default_error_condition(int c) const noexcept override final
151  {
152  switch (static_cast<StatusCode>(c)) {
153  case StatusCode::OK:
154  return std::error_condition();
156  return std::make_error_condition(std::errc::argument_out_of_domain);
161  return std::make_error_condition(std::errc::invalid_argument);
163  return std::make_error_condition(std::errc::no_such_file_or_directory);
164  default:
165  return std::error_condition(c, *this);
166  }
167  }
168 };
169 } // namespace details
170 
175 {
177  return c;
178 }
179 
184 inline std::error_code make_error_code(StatusCode e)
185 {
186  return {static_cast<int>(e), status_code_category()};
187 }
188 
196 class IOStatus
197 {
198 public:
202  IOStatus() = default;
203 
209  IOStatus(std::error_code ec, std::string msg = {})
210  : m_ec(ec)
211  , m_msg(std::move(msg))
212  {
213  }
214 
219  bool operator==(const IOStatus& other) const
220  {
221  return other.m_ec == m_ec;
222  }
223  bool operator!=(const IOStatus& other) const
224  {
225  return !(*this == other);
226  }
232  const std::error_code& code() const
233  {
234  return m_ec;
235  }
236 
240  const std::string& message() const
241  {
242  return m_msg;
243  }
244 
251  bool is_ok() const
252  {
253  return !(bool)m_ec;
254  }
255  operator bool() const
256  {
257  return is_ok();
258  }
266  bool is_error() const
267  {
268  return !is_ok();
269  }
270 
274  std::string formatted_message() const
275  {
276  if (is_error()) {
277  return m_ec.message() + ": " + m_msg;
278  }
279  return {};
280  }
281 
282 private:
283  std::error_code m_ec = {};
284  std::string m_msg = {};
285 };
286 
290 inline void PrintTo(const IOStatus& status, std::ostream* os)
291 {
292  *os << status.formatted_message();
293 }
294 
299 inline const std::error_code& make_error_code(const IOStatus& status)
300 {
301  return status.code();
302 }
303 
352 template <class T>
353 using IOResult = boost::outcome_v2::unchecked<T, IOStatus>;
354 
359 inline auto success()
360 {
362 }
363 
369 template <class T>
370 auto success(T&& t)
371 {
372  return boost::outcome_v2::success(std::forward<T>(t));
373 }
374 
380 inline auto failure(const IOStatus& s)
381 {
382  return boost::outcome_v2::failure(s);
383 }
384 
391 inline auto failure(std::error_code c, const std::string& msg = "")
392 {
393  return failure(IOStatus{c, msg});
394 }
395 
406 template <class T>
407 using Tag = boost::outcome_v2::in_place_type_t<T>;
408 
409 //forward declaration only, see below
410 template <class IOContext, class T>
411 void serialize(IOContext& io, const T& t);
412 
413 //forward declaration only, see below
414 template <class IOContext, class T>
415 IOResult<T> deserialize(IOContext& io, Tag<T> tag);
416 
417 //utility for apply function implementation
418 namespace details
419 {
420 //multiple nested IOResults are redundant and difficult to use.
421 //so transform IOResult<IOResult<T>> into IOResult<T>.
422 template <class T>
424  using Type = IOResult<T>;
425 };
426 template <class T>
428  using Type = typename FlattenIOResult<T>::Type;
429 };
430 template <class T>
432 
433 //check if a type is an IOResult or something else
434 template <class T>
435 struct IsIOResult : std::false_type {
436 };
437 template <class T>
438 struct IsIOResult<IOResult<T>> : std::true_type {
439 };
440 
441 //if F(T...) returns an IOResult<U>, apply returns that directly
442 //if F(T...) returns a different type U, apply returns IOResult<U>
443 template <class F, class... T>
444 using ApplyResultT = FlattenIOResultT<std::invoke_result_t<F, T...>>;
445 
447 template <class F, class... T>
448 ApplyResultT<F, T...> eval(F f, const IOResult<T>&... rs)
449 {
450  if constexpr (IsIOResult<std::invoke_result_t<F, T...>>::value) {
451  // case for functions that do internal validation
452  return f(rs.value()...);
453  }
454  else {
455  // case for functions that can't fail, because all values are acceptable
456  return success(f(rs.value()...));
457  }
458 }
459 
460 } // namespace details
461 
480 template <class IOContext, class F, class... T>
481 details::ApplyResultT<F, T...> apply(IOContext& io, F f, const IOResult<T>&... rs)
482 {
483  //store the status of each argument in an array to check them.
484  //it would be possible to write a recursive template that checks one argument
485  //after the other without copying each status. This would probably be slightly faster
486  //and easier to optimize for the compiler. but gdb crashes trying to resolve the
487  //very long symbol in case of many arguments. Successful results are very cheap to copy
488  //and slightly worse performance in the case of an error is probably acceptable.
489 
490  //check for errors in the arguments
491  auto status = std::array{(rs ? IOStatus{} : rs.error())...};
492  auto iter_err = std::find_if(std::begin(status), std::end(status), [](auto& s) {
493  return s.is_error();
494  });
495 
496  //evaluate f if all succesful
497  auto result =
498  iter_err == std::end(status) ? details::eval(f, rs...) : details::ApplyResultT<F, T...>(failure(*iter_err));
499 
500  if (!result) {
501  //save error in context
502  //if the error was generated by the applied function, the context hasn't seen it yet.
503  //this allows the context to fail fast and not continue to parse more.
504  io.set_error(result.error());
505  }
506  return result;
507 }
508 
509 template <class IOContext, class F, class... T>
510 details::ApplyResultT<F, T...> apply(IOContext& io, F f, const std::tuple<IOResult<T>...>& results)
511 {
512  return std::apply(
513  [&](auto&&... rs) {
514  return apply(io, f, rs...);
515  },
516  results);
517 }
520 //detect a serialize member function
521 template <class T, class IOContext>
522 concept HasSerialize = requires(IOContext& ctxt, const T& t) { t.serialize(ctxt); };
523 
524 //detect a static deserialize member function
525 template <class T, class IOContext>
526 concept HasDeserialize = requires(IOContext& ctxt) {
527  { T::deserialize(ctxt) } -> std::same_as<IOResult<T>>;
528 };
529 
530 //utility for (de-)serializing tuple-like objects
531 namespace details
532 {
533 template <size_t Idx>
535 {
536  return "Element" + std::to_string(Idx);
537 }
538 
539 //recursive tuple serialization for each tuple element
540 //store one tuple element after the other in the IOObject
541 template <size_t Idx, class IOObj, class Tup>
542 void serialize_tuple_element(IOObj& obj, const Tup& tup)
543 {
544  if constexpr (Idx < std::tuple_size_v<Tup>) {
545  //serialize one element, then recurse
546  obj.add_element(make_tuple_element_name<Idx>(), std::get<Idx>(tup));
547  serialize_tuple_element<Idx + 1>(obj, tup);
548  }
549  // else: end of recursion, no more elements to serialize
550 }
551 
552 //recursive tuple deserialization for each tuple element
553 //read one tuple element after the other from the IOObject
554 //argument pack rs contains the elements that have been read already
555 template <class IOObj, class Tup, class... Ts>
557 {
558  if constexpr (sizeof...(Ts) < std::tuple_size_v<Tup>) {
559  //get the next element of the tuple from the IO object
560  const size_t Idx = sizeof...(Ts);
561  auto r = obj.expect_element(make_tuple_element_name<Idx>(), Tag<std::tuple_element_t<Idx, Tup>>{});
562  //recurse, append the new element to the pack of arguments
563  return deserialize_tuple_element(obj, tag, rs..., r);
564  }
565  else {
566  //end of recursion
567  //number of arguments in rs is the same as the size of the tuple
568  //no more elements to read, so finalize the object
569  return mio::apply(
570  obj,
571  [](const Ts&... ts) {
572  return Tup(ts...);
573  },
574  rs...);
575  }
576 }
577 
578 } // namespace details
579 
587 template <class IOContext, template <class...> class Tup, class... T>
588  requires std::same_as<Tup<T...>, std::pair<T...>> || std::same_as<Tup<T...>, std::tuple<T...>>
589 void serialize_internal(IOContext& io, const Tup<T...>& tup)
590 {
591  auto obj = io.create_object("Tuple");
592  details::serialize_tuple_element<0>(obj, tup);
593 }
594 
603 template <class IOContext, template <class...> class Tup, class... T>
604  requires std::same_as<Tup<T...>, std::pair<T...>> || std::same_as<Tup<T...>, std::tuple<T...>>
605 IOResult<Tup<T...>> deserialize_internal(IOContext& io, Tag<Tup<T...>> tag)
606 {
607  auto obj = io.expect_object("Tuple");
608  return details::deserialize_tuple_element(obj, tag);
609 }
610 
618 template <class IOContext, class M>
619 void serialize_internal(IOContext& io, const Eigen::EigenBase<M>& mat)
620 {
621  auto obj = io.create_object("Matrix");
622  obj.add_element("Rows", mat.rows());
623  obj.add_element("Columns", mat.cols());
624  obj.add_list("Elements", begin(static_cast<const M&>(mat)), end(static_cast<const M&>(mat)));
625 }
626 
637 template <class IOContext, IsMatrixExpression M>
638 IOResult<M> deserialize_internal(IOContext& io, Tag<M> /*tag*/)
639 {
640  auto obj = io.expect_object("Matrix");
641  auto rows = obj.expect_element("Rows", Tag<Eigen::Index>{});
642  auto cols = obj.expect_element("Columns", Tag<Eigen::Index>{});
643  auto elements = obj.expect_list("Elements", Tag<typename M::Scalar>{});
644  return mio::apply(
645  io,
646  [](auto&& r, auto&& c, auto&& v) {
647  auto m = M{r, c};
648  for (auto i = Eigen::Index(0); i < r; ++i) {
649  for (auto j = Eigen::Index(0); j < c; ++j) {
650  m(i, j) = v[i * c + j];
651  }
652  }
653  return m;
654  },
655  rows, cols, elements);
656 }
657 
665 template <class IOContext, size_t N>
666 void serialize_internal(IOContext& io, const std::bitset<N> bitset)
667 {
668  std::array<bool, N> bits;
669  for (size_t i = 0; i < N; i++) {
670  bits[i] = bitset[i];
671  }
672  auto obj = io.create_object("BitSet");
673  obj.add_list("bitset", bits.begin(), bits.end());
674 }
675 
684 template <class IOContext, size_t N>
685 IOResult<std::bitset<N>> deserialize_internal(IOContext& io, Tag<std::bitset<N>> tag)
686 {
687  mio::unused(tag);
688  auto obj = io.expect_object("BitSet");
689  auto bits = obj.expect_list("bitset", Tag<bool>{});
690 
691  return apply(
692  io,
693  [](auto&& bits_) -> IOResult<std::bitset<N>> {
694  if (bits_.size() != N) {
695  return failure(StatusCode::InvalidValue,
696  "Incorrent number of booleans to deserialize bitset. Expected " + std::to_string(N) +
697  ", got " + std::to_string(bits_.size()) + ".");
698  }
699  std::bitset<N> bitset;
700  for (size_t i = 0; i < N; i++) {
701  bitset[i] = bits_[i];
702  }
703  return bitset;
704  },
705  bits);
706 }
707 
715 template <class IOContext, class E>
716  requires std::is_enum_v<E>
717 void serialize_internal(IOContext& io, E e)
718 {
719  mio::serialize(io, std::underlying_type_t<E>(e));
720 }
721 
732 template <class IOContext, class E>
733  requires std::is_enum_v<E>
734 IOResult<E> deserialize_internal(IOContext& io, Tag<E> /*tag*/)
735 {
736  BOOST_OUTCOME_TRY(auto&& i, mio::deserialize(io, mio::Tag<std::underlying_type_t<E>>{}));
737  return success(E(i));
738 }
739 
747 template <class IOContext, HasSerialize<IOContext> T>
748 void serialize_internal(IOContext& io, const T& t)
749 {
750  t.serialize(io);
751 }
752 
761 template <class IOContext, HasDeserialize<IOContext> T>
762 IOResult<T> deserialize_internal(IOContext& io, Tag<T> /*tag*/)
763 {
764  return T::deserialize(io);
765 }
766 
772 template <class C>
773 concept IsContainer =
774  requires(C c, const C& cc) {
775  cc.begin() != cc.end();
776  C(c.begin(), c.end());
777  }
778  // Eigen types may pass as container, but we want to handle them separately
779  && !std::is_base_of_v<Eigen::EigenBase<C>, C>;
780 
788 template <class IOContext, IsContainer Container>
789  requires(!HasSerialize<IOContext, Container>)
790 void serialize_internal(IOContext& io, const Container& container)
791 {
792  auto obj = io.create_object("List");
793  obj.add_list("Items", container.begin(), container.end());
794 } // namespace mio
795 
804 template <class IOContext, IsContainer Container>
805  requires(!HasSerialize<IOContext, Container>)
807 {
808  auto obj = io.expect_object("List");
809  auto i = obj.expect_list("Items", Tag<typename Container::value_type>{});
810  return apply(
811  io,
812  [](auto&& i_) {
813  return Container(i_.begin(), i_.end());
814  },
815  i);
816 }
817 
835 template <class IOContext, class T>
836 void serialize(IOContext& io, const T& t)
837 {
839  serialize_internal(io, t);
840 }
841 
859 template <class IOContext, class T>
860 IOResult<T> deserialize(IOContext& io, Tag<T> tag)
861 {
863  return deserialize_internal(io, tag);
864 }
865 
869 std::string get_current_dir_name();
870 
877 IOResult<bool> create_directory(std::string const& rel_path, std::string& abs_path);
878 
884 IOResult<bool> create_directory(std::string const& rel_path);
885 
893 bool file_exists(std::string const& rel_path, std::string& abs_path);
894 
895 } // namespace mio
896 
897 #endif // MIO_IO_IO_H
IOStatus represents the result of an operation.
Definition: io.h:197
const std::string & message() const
get the message that contains additional information about errors.
Definition: io.h:240
const std::error_code & code() const
get the error code.
Definition: io.h:232
IOStatus(std::error_code ec, std::string msg={})
constructor with error code and message
Definition: io.h:209
std::string m_msg
Check if the status represents failure.
Definition: io.h:284
std::string formatted_message() const
Get a string that combines the error code and the message.
Definition: io.h:274
bool operator!=(const IOStatus &other) const
equality comparison operators.
Definition: io.h:223
IOStatus()=default
default constructor, represents success
std::error_code m_ec
Check if the status represents failure.
Definition: io.h:283
bool is_ok() const
Check if the status represents success.
Definition: io.h:251
bool operator==(const IOStatus &other) const
equality comparison operators.
Definition: io.h:219
bool is_error() const
Check if the status represents failure.
Definition: io.h:266
category that describes StatusCode in std::error_code
Definition: io.h:112
virtual std::error_condition default_error_condition(int c) const noexcept override final
convert to standard error code to make it comparable.
Definition: io.h:150
virtual std::string message(int c) const override final
convert enum to string message.
Definition: io.h:125
virtual const char * name() const noexcept override final
name of the status
Definition: io.h:117
trait_value< T >::RETURN_TYPE & value(T &x)
Definition: ad.hpp:3308
void serialize_tuple_element(IOObj &obj, const Tup &tup)
Definition: io.h:542
std::string make_tuple_element_name()
Definition: io.h:534
ApplyResultT< F, T... > eval(F f, const IOResult< T > &... rs)
Evaluates a function f using values of the given IOResults as arguments, assumes all IOResults are su...
Definition: io.h:448
FlattenIOResultT< std::invoke_result_t< F, T... > > ApplyResultT
Definition: io.h:444
typename FlattenIOResult< T >::Type FlattenIOResultT
Definition: io.h:431
IOResult< Tup > deserialize_tuple_element(IOObj &obj, Tag< Tup > tag, const IOResult< Ts > &... rs)
Definition: io.h:556
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
requires details::IsElementReference< M > RowMajorIterator< M, false > end(M &m)
create a non-const end iterator for the matrix m.
Definition: eigen_util.h:449
IOResult< bool > create_directory(std::string const &rel_path, std::string &abs_path)
Creates a directory in the file system.
Definition: io.cpp:37
concept HasSerialize
Definition: io.h:522
IOResult< T > deserialize(IOContext &io, Tag< T > tag)
Restores an object from the data stored in an IO context.
Definition: io.h:860
auto failure(const IOStatus &s)
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:380
std::string get_current_dir_name()
Returns the current working directory name.
Definition: io.cpp:31
StatusCode
code to indicate the result of an operation.
Definition: io.h:49
void serialize(IOContext &io, const T &t)
Save data that describes an object in a format determined by the given context.
Definition: io.h:836
boost::outcome_v2::in_place_type_t< T > Tag
Type that is used for overload resolution.
Definition: io.h:407
auto i
Definition: io.h:809
details::ApplyResultT< F, T... > apply(IOContext &io, F f, const IOResult< T > &... rs)
Evaluate a function with zero or more unpacked IOResults as arguments.
Definition: io.h:481
std::error_code make_error_code(StatusCode e)
Convert StatusCode to std::error_code.
Definition: io.h:184
requires(!std::is_trivial_v< T >) void BinarySerializerObject
Definition: binary_serializer.h:333
concept HasDeserialize
Definition: io.h:526
const details::StatusCodeCategory & status_code_category()
singleton StatusCodeCategory instance.
Definition: io.h:174
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:359
return apply(io, [](auto &&i_) { return Container(i_.begin(), i_.end());}, i)
auto success(T &&t)
Create an object that is implicitly convertible to a succesful IOResult.
Definition: io.h:370
void unused(T &&...)
Does nothing, can be used to mark variables as not used.
Definition: compiler_diagnostics.h:30
requires details::IsElementReference< M > RowMajorIterator< M, false > begin(M &m)
create a non-const iterator to first element of the matrix m.
Definition: eigen_util.h:421
bool file_exists(std::string const &rel_path, std::string &abs_path)
Check if a file exists.
Definition: io.cpp:66
concept IsContainer
Concept to check whether C is a STL compatible container.
Definition: io.h:773
const Container & container
Definition: io.h:791
IOFlags
flags to determine the behavior of the serialization process.
Definition: io.h:66
@ IOF_None
default behavior.
Definition: io.h:70
@ IOF_OmitDistributions
Don't serialize distributions for types that contain both a specific value and a distribution from wh...
Definition: io.h:76
@ IOF_OmitValues
Don't serialize the current value for types that contain both a specific value and a distribution fro...
Definition: io.h:82
@ IOF_IncludeTypeInfo
Include type info in the serialization.
Definition: io.h:88
void PrintTo(const IOStatus &status, std::ostream *os)
gtest printer for IOStatus.
Definition: io.h:290
auto failure(std::error_code c, const std::string &msg="")
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:391
boost::outcome_v2::unchecked< T, IOStatus > IOResult
Value-or-error type for operations that return a value but can fail.
Definition: io.h:353
IOResult< T > deserialize_internal(IOContext &io, Tag< T > tag)
Deserialization implementation for the default serialization feature.
Definition: default_serialize.h:236
void serialize_internal(IOContext &io, const T &a)
Serialization implementation for the default serialization feature.
Definition: default_serialize.h:213
Definition: io.h:94
typename FlattenIOResult< T >::Type Type
Definition: io.h:428
Definition: io.h:423
IOResult< T > Type
Definition: io.h:424
Definition: io.h:435