palimpsest — Fast serializable C++ dictionaries  v2.2.0
Dictionary.h
Go to the documentation of this file.
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright 2022 Stéphane Caron
3 // Copyright 2024 Inria
4 /*
5  * This file incorporates work covered by the following copyright and
6  * permission notice:
7  *
8  * Configuration and DataStore classes of mc_rtc
9  * Copyright 2015-2020 CNRS-UM LIRMM, CNRS-AIST JRL
10  * SPDX-License-Identifier: BSD-2-Clause
11  */
12 
13 #pragma once
14 
15 #include <spdlog/spdlog.h>
16 
17 #include <Eigen/Core>
18 #include <functional>
19 #include <memory>
20 #include <stdexcept>
21 #include <string>
22 #include <unordered_map>
23 #include <utility>
24 #include <vector>
25 
26 #include "palimpsest/exceptions/TypeError.h"
27 #include "palimpsest/internal/Allocator.h"
28 #include "palimpsest/internal/is_valid_hash.h"
29 #include "palimpsest/internal/type_name.h"
30 #include "palimpsest/json/write.h"
31 #include "palimpsest/mpack/Writer.h"
32 #include "palimpsest/mpack/read.h"
33 #include "palimpsest/mpack/write.h"
34 
35 namespace palimpsest {
36 
37 using exceptions::TypeError;
38 
97 class Dictionary {
102  class Value {
103  public:
105  Value() = default;
106 
108  Value(const Value &) = delete;
109 
111  Value &operator=(const Value &) = delete;
112 
114  Value(Value &&) = default;
115 
117  Value &operator=(Value &&) = default;
118 
120  ~Value() {
121  if (this->buffer) {
122  destroy_(*this);
123  }
124  }
125 
127  template <typename T>
128  void allocate() {
129  this->buffer.reset(
130  reinterpret_cast<uint8_t *>(internal::Allocator<T>().allocate(1)));
131  }
132 
138  void deserialize(mpack_node_t node) { deserialize_(*this, node); }
139 
144  void print(std::ostream &stream) const { print_(*this, stream); }
145 
150  void serialize(mpack::Writer &writer) const {
151  serialize_(*this, writer.mpack_writer());
152  }
153 
158  template <typename T, typename... ArgsT>
159  T &setup() {
160  this->type_name = &internal::type_name<T>;
161  this->same = &internal::is_valid_hash<T, ArgsT...>;
162  deserialize_ = [](Value &self, mpack_node_t node) {
163  T *cast_buffer = reinterpret_cast<T *>(self.buffer.get());
164  mpack::read<T>(node, *cast_buffer);
165  };
166  destroy_ = [](Value &self) {
167  T *p = reinterpret_cast<T *>(self.buffer.release());
168  p->~T();
169  internal::Allocator<T>().deallocate(p, 1);
170  };
171  print_ = [](const Value &self, std::ostream &stream) {
172  const T *cast_buffer = reinterpret_cast<const T *>(self.buffer.get());
173  json::write<T>(stream, *cast_buffer);
174  };
175  serialize_ = [](const Value &self, mpack_writer_t *writer) {
176  const T *cast_buffer = reinterpret_cast<const T *>(self.buffer.get());
177  mpack::write<T>(writer, *cast_buffer);
178  };
179  return *(reinterpret_cast<T *>(this->buffer.get()));
180  }
181 
188  template <typename T>
189  T &get_reference() const {
190  if (!this->same(typeid(T).hash_code())) {
191  std::string cast_type = this->type_name();
192  throw TypeError(__FILE__, __LINE__,
193  "Object has type \"" + cast_type +
194  "\" but is being cast to type \"" +
195  typeid(T).name() + "\".");
196  }
197  return *(reinterpret_cast<T *>(this->buffer.get()));
198  }
199 
200  public:
202  std::unique_ptr<uint8_t[]> buffer = nullptr;
203 
205  const char *(*type_name)();
206 
208  bool (*same)(std::size_t);
209 
210  private:
212  void (*deserialize_)(Value &, mpack_node_t);
213 
215  void (*destroy_)(Value &);
216 
218  void (*print_)(const Value &, std::ostream &);
219 
221  void (*serialize_)(const Value &, mpack_writer_t *);
222  };
223 
224  public:
226  Dictionary() = default;
227 
229  Dictionary(const Dictionary &) = delete;
230 
232  Dictionary &operator=(const Dictionary &) = delete;
233 
235  Dictionary(Dictionary &&) = default;
236 
239 
245  ~Dictionary() = default;
246 
248  bool is_map() const noexcept { return (value_.buffer == nullptr); }
249 
251  bool is_empty() const noexcept { return (is_map() && map_.size() < 1); }
252 
254  bool is_value() const noexcept { return (value_.buffer != nullptr); }
255 
261  bool has(const std::string &key) const noexcept {
262  return (map_.find(key) != map_.end());
263  }
264 
266  std::vector<std::string> keys() const noexcept;
267 
269  unsigned size() const noexcept { return map_.size(); }
270 
278  template <typename T>
279  T &as() {
280  if (!this->is_value()) {
281  throw TypeError(__FILE__, __LINE__, "Object is not a value.");
282  }
283  return value_.get_reference<T>();
284  }
285 
292  template <typename T>
293  const T &as() const {
294  if (!this->is_value()) {
295  throw TypeError(__FILE__, __LINE__, "Object is not a value.");
296  }
297  return const_cast<const T &>(value_.get_reference<T>());
298  }
299 
308  template <typename T>
309  T &get(const std::string &key) {
310  return const_cast<T &>(get_<T>(key));
311  }
312 
321  template <typename T>
322  const T &get(const std::string &key) const {
323  return get_<T>(key);
324  }
325 
336  template <typename T>
337  const T &get(const std::string &key, const T &default_value) const {
338  auto it = map_.find(key);
339  if (it != map_.end()) {
340  if (it->second->is_map()) {
341  throw TypeError(__FILE__, __LINE__,
342  "Object at key \"" + key +
343  "\" is a dictionary, cannot get a single value "
344  "from it. Did you "
345  "mean to use operator()?");
346  }
347  try {
348  return it->second->value_.get_reference<T>();
349  } catch (const TypeError &e) {
350  throw TypeError(
351  __FILE__, __LINE__,
352  "Object for key \"" + key +
353  "\" does not have the same type as the stored type. Stored " +
354  it->second->value_.type_name() + " but requested " +
355  typeid(T).name() + ".");
356  }
357  }
358  return default_value;
359  }
360 
376  template <typename T, typename... ArgsT, typename... Args>
377  T &insert(const std::string &key, Args &&...args) {
378  if (this->is_value()) {
379  throw TypeError(__FILE__, __LINE__,
380  "Cannot insert at key \"" + key +
381  "\" in non-dictionary object of type \"" +
382  value_.type_name() + "\".");
383  }
384  auto &child = this->operator()(key);
385  if (!child.is_empty()) {
386  spdlog::warn(
387  "[Dictionary::insert] Key \"{}\" already exists. Returning existing "
388  "value rather than creating a new one.",
389  key);
390  return get<T>(key);
391  }
392  child.value_.allocate<T>();
393  new (child.value_.buffer.get()) T(std::forward<Args>(args)...);
394  T &ret = child.value_.setup<T, ArgsT...>();
395  return ret;
396  }
397 
408  template <typename T>
409  Dictionary &operator=(const T &new_value) {
410  if (this->is_map()) {
411  clear();
412  }
413  if (this->is_empty()) {
414  become<T>(new_value);
415  return *this;
416  }
417  auto &internal_value = value_.get_reference<T>();
418  internal_value = new_value;
419  return *this;
420  }
421 
431  Dictionary &operator=(const char *c_string) {
432  return operator=<std::string>(std::string(c_string));
433  }
434 
439  void remove(const std::string &key) noexcept;
440 
442  void clear() noexcept;
443 
471  Dictionary &operator()(const std::string &key);
472 
487  const Dictionary &operator()(const std::string &key) const;
488 
495  size_t serialize(std::vector<char> &buffer) const;
496 
501  void write(const std::string &filename) const;
502 
507  void read(const std::string &filename);
508 
517  void update(const char *data, size_t size);
518 
528  void update(mpack_node_t node);
529 
531  operator bool &() { return this->as<bool>(); }
532 
534  operator const bool &() const { return this->as<bool>(); }
535 
537  operator int8_t &() { return this->as<int8_t>(); }
538 
540  operator const int8_t &() const { return this->as<int8_t>(); }
541 
543  operator int16_t &() { return this->as<int16_t>(); }
544 
546  operator const int16_t &() const { return this->as<int16_t>(); }
547 
549  operator int32_t &() { return this->as<int32_t>(); }
550 
552  operator const int32_t &() const { return this->as<int32_t>(); }
553 
555  operator int64_t &() { return this->as<int64_t>(); }
556 
558  operator const int64_t &() const { return this->as<int64_t>(); }
559 
561  operator uint8_t &() { return this->as<uint8_t>(); }
562 
564  operator const uint8_t &() const { return this->as<uint8_t>(); }
565 
567  operator uint16_t &() { return this->as<uint16_t>(); }
568 
570  operator const uint16_t &() const { return this->as<uint16_t>(); }
571 
573  operator uint32_t &() { return this->as<uint32_t>(); }
574 
576  operator const uint32_t &() const { return this->as<uint32_t>(); }
577 
579  operator uint64_t &() { return this->as<uint64_t>(); }
580 
582  operator const uint64_t &() const { return this->as<uint64_t>(); }
583 
585  operator float &() { return this->as<float>(); }
586 
588  operator const float &() const { return this->as<float>(); }
589 
591  operator double &() { return this->as<double>(); }
592 
594  operator const double &() const { return this->as<double>(); }
595 
597  operator std::string &() { return this->as<std::string>(); }
598 
600  operator const std::string &() const { return this->as<std::string>(); }
601 
603  operator Eigen::Vector2d &() { return this->as<Eigen::Vector2d>(); }
604 
606  operator const Eigen::Vector2d &() const {
607  return this->as<Eigen::Vector2d>();
608  }
609 
611  operator Eigen::Vector3d &() { return this->as<Eigen::Vector3d>(); }
612 
614  operator const Eigen::Vector3d &() const {
615  return this->as<Eigen::Vector3d>();
616  }
617 
619  operator Eigen::VectorXd &() { return this->as<Eigen::VectorXd>(); }
620 
622  operator const Eigen::VectorXd &() const {
623  return this->as<Eigen::VectorXd>();
624  }
625 
627  operator Eigen::Quaterniond &() { return this->as<Eigen::Quaterniond>(); }
628 
630  operator const Eigen::Quaterniond &() const {
631  return this->as<Eigen::Quaterniond>();
632  }
633 
635  operator Eigen::Matrix3d &() { return this->as<Eigen::Matrix3d>(); }
636 
638  operator const Eigen::Matrix3d &() const {
639  return this->as<Eigen::Matrix3d>();
640  }
641 
648  friend std::ostream &operator<<(std::ostream &stream, const Dictionary &dict);
649 
650  protected:
651  template <typename T, typename... ArgsT, typename... Args>
652  void become(Args &&...args) {
653  assert(this->is_empty());
654  value_.allocate<T>();
655  new (value_.buffer.get()) T(std::forward<Args>(args)...);
656  value_.setup<T, ArgsT...>();
657  }
658 
659  private:
668  template <typename T>
669  const T &get_(const std::string &key) const {
670  const auto &child_value = get_child_value_(key);
671  try {
672  return child_value.get_reference<T>();
673  } catch (const TypeError &e) {
674  throw TypeError(__FILE__, __LINE__,
675  "Object at key \"" + key + "\" has type \"" +
676  child_value.type_name() +
677  "\", but is being cast to type \"" +
678  typeid(T).name() + "\".");
679  }
680  }
681 
690  const Value &get_child_value_(const std::string &key) const;
691 
699  void insert_at_key_(const std::string &key, const mpack_node_t &value);
700 
705  void serialize_(mpack::Writer &writer) const;
706 
707  protected:
709  Value value_;
710 
712  std::unordered_map<std::string, std::unique_ptr<Dictionary>> map_;
713 };
714 
715 } // namespace palimpsest
716 
717 namespace fmt {
718 
720 template <>
721 struct formatter<palimpsest::Dictionary> : public formatter<string_view> {
722  template <typename FormatContext>
723  auto format(const palimpsest::Dictionary &dict, FormatContext &ctx)
724  -> decltype(ctx.out()) {
725  std::ostringstream oss;
726  oss << dict;
727  return formatter<string_view>::format(oss.str(), ctx);
728  }
729 };
730 
731 } // namespace fmt
Dictionary of values and sub-dictionaries.
Definition: Dictionary.h:97
Dictionary & operator=(const T &new_value)
Assign value directly.
Definition: Dictionary.h:409
Dictionary(Dictionary &&)=default
Default move constructor.
Value value_
Internal value, used if we are a value.
Definition: Dictionary.h:709
std::unordered_map< std::string, std::unique_ptr< Dictionary > > map_
Key-value map, used if we are a map.
Definition: Dictionary.h:712
T & as()
Get reference to the internal value.
Definition: Dictionary.h:279
void read(const std::string &filename)
Update dictionary from a MessagePack binary file.
Definition: Dictionary.cpp:224
bool is_map() const noexcept
We are a (potentially empty) map if and only if the value is empty.
Definition: Dictionary.h:248
void update(const char *data, size_t size)
Update dictionary from raw MessagePack data.
Definition: Dictionary.cpp:39
unsigned size() const noexcept
Return the number of keys in the dictionary.
Definition: Dictionary.h:269
const T & as() const
Const variant of as.
Definition: Dictionary.h:293
bool is_empty() const noexcept
We are empty if and only if we are a dictionary with no element.
Definition: Dictionary.h:251
T & get(const std::string &key)
Get reference to the object at a given key.
Definition: Dictionary.h:309
const T & get(const std::string &key, const T &default_value) const
Get object at a given key if it exists, or a default value otherwise.
Definition: Dictionary.h:337
Dictionary()=default
Default constructor.
friend std::ostream & operator<<(std::ostream &stream, const Dictionary &dict)
Output stream operator for printing.
Definition: Dictionary.cpp:279
void remove(const std::string &key) noexcept
Remove a key-value pair from the dictionary.
Definition: Dictionary.cpp:189
Dictionary(const Dictionary &)=delete
No copy constructor.
Dictionary & operator=(const char *c_string)
Assignment operator for C-style strings.
Definition: Dictionary.h:431
const T & get(const std::string &key) const
Const variant of get.
Definition: Dictionary.h:322
void become(Args &&...args)
Definition: Dictionary.h:652
bool has(const std::string &key) const noexcept
Check whether a key is in the dictionary.
Definition: Dictionary.h:261
~Dictionary()=default
Default destructor.
T & insert(const std::string &key, Args &&...args)
Create an object at a given key and return a reference to it.
Definition: Dictionary.h:377
Dictionary & operator()(const std::string &key)
Return a reference to the dictionary at key, performing an insertion if such a key does not already e...
Definition: Dictionary.cpp:198
size_t serialize(std::vector< char > &buffer) const
Serialize to raw MessagePack data.
Definition: Dictionary.cpp:245
std::vector< std::string > keys() const noexcept
Return the list of keys of the dictionary.
Definition: Dictionary.cpp:180
Dictionary & operator=(Dictionary &&)=default
Default move assignment operator.
Dictionary & operator=(const Dictionary &)=delete
No copy assignment operator.
void write(const std::string &filename) const
Write MessagePack serialization to a binary file.
Definition: Dictionary.cpp:235
void clear() noexcept
Remove all entries from the dictionary.
Definition: Dictionary.cpp:34
bool is_value() const noexcept
We are a value if and only if the internal value is non-empty.
Definition: Dictionary.h:254
auto format(const palimpsest::Dictionary &dict, FormatContext &ctx) -> decltype(ctx.out())
Definition: Dictionary.h:723