safe_any.hpp
Go to the documentation of this file.
1 /* Copyright (C) 2022 Davide Faconti - All Rights Reserved
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
5 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
10 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 */
12 
13 #pragma once
14 
15 #if __has_include(<charconv>)
16 #include <charconv>
17 #endif
18 
19 #include <string>
20 #include <type_traits>
21 #include <typeindex>
22 
28 
29 namespace BT
30 {
31 
32 static std::type_index UndefinedAnyType = typeid(nullptr);
33 
34 // Rational: since type erased numbers will always use at least 8 bytes
35 // it is faster to cast everything to either double, uint64_t or int64_t.
36 class Any
37 {
38  template <typename T>
41 
42  template <typename T>
45 
46  template <typename T>
47  using EnableString =
49 
50  template <typename T>
52 
53  template <typename T>
55 
56  template <typename T>
57  using EnableUnknownType =
60 
61  template <typename T>
62  nonstd::expected<T, std::string> stringToNumber() const;
63 
64 public:
66  {}
67 
68  ~Any() = default;
69 
70  Any(const Any& other) : _any(other._any), _original_type(other._original_type)
71  {}
72 
73  Any(Any&& other) : _any(std::move(other._any)), _original_type(other._original_type)
74  {}
75 
76  explicit Any(const double& value) : _any(value), _original_type(typeid(double))
77  {}
78 
79  explicit Any(const uint64_t& value) : _any(value), _original_type(typeid(uint64_t))
80  {}
81 
82  explicit Any(const float& value) : _any(double(value)), _original_type(typeid(float))
83  {}
84 
85  explicit Any(const std::string& str)
86  : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
87  {}
88 
89  explicit Any(const char* str)
90  : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
91  {}
92 
93  explicit Any(const SafeAny::SimpleString& str)
94  : _any(str), _original_type(typeid(std::string))
95  {}
96 
97  explicit Any(const std::string_view& str)
98  : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
99  {}
100 
101  // all the other integrals are casted to int64_t
102  template <typename T>
103  explicit Any(const T& value, EnableIntegral<T> = 0)
104  : _any(int64_t(value)), _original_type(typeid(T))
105  {}
106 
107  Any(const std::type_index& type) : _original_type(type)
108  {}
109 
110  // default for other custom types
111  template <typename T>
112  explicit Any(const T& value, EnableNonIntegral<T> = 0)
113  : _any(value), _original_type(typeid(T))
114  {
115  static_assert(!std::is_reference<T>::value, "Any can not contain references");
116  }
117 
118  Any& operator=(const Any& other);
119 
120  [[nodiscard]] bool isNumber() const;
121 
122  [[nodiscard]] bool isIntegral() const;
123 
124  [[nodiscard]] bool isString() const
125  {
126  return _any.type() == typeid(SafeAny::SimpleString);
127  }
128 
129  // check is the original type is equal to T
130  template <typename T>
131  [[nodiscard]] bool isType() const
132  {
133  return _original_type == typeid(T);
134  }
135 
136  // copy the value (casting into dst). We preserve the destination type.
137  void copyInto(Any& dst);
138 
139  // this is different from any_cast, because if allows safe
140  // conversions between arithmetic values and from/to string.
141  template <typename T>
142  nonstd::expected<T, std::string> tryCast() const;
143 
144  // same as tryCast, but throws if fails
145  template <typename T>
146  [[nodiscard]] T cast() const
147  {
148  if(auto res = tryCast<T>())
149  {
150  return res.value();
151  }
152  else
153  {
154  throw std::runtime_error(res.error());
155  }
156  }
157 
158  // Method to access the value by pointer.
159  // It will return nullptr, if the user try to cast it to a
160  // wrong type or if Any was empty.
161  template <typename T>
162  [[nodiscard]] T* castPtr()
163  {
164  static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
165  "[double]. "
166  "Use that instead");
167  static_assert(!SafeAny::details::is_integer<T>() || std::is_same_v<T, uint64_t>, "The"
168  " va"
169  "lue"
170  " ha"
171  "s "
172  "bee"
173  "n "
174  "cas"
175  "ted"
176  " in"
177  "ter"
178  "nal"
179  "ly "
180  "to "
181  "[in"
182  "t64"
183  "_t]"
184  ". "
185  "Use"
186  " th"
187  "at "
188  "ins"
189  "tea"
190  "d");
191 
192  return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
193  }
194 
195  // This is the original type
196  [[nodiscard]] const std::type_index& type() const noexcept
197  {
198  return _original_type;
199  }
200 
201  // This is the type we casted to, internally
202  [[nodiscard]] const std::type_info& castedType() const noexcept
203  {
204  return _any.type();
205  }
206 
207  [[nodiscard]] bool empty() const noexcept
208  {
209  return _any.empty();
210  }
211 
212 private:
214  std::type_index _original_type;
215 
216  //----------------------------
217 
218  template <typename DST>
219  nonstd::expected<DST, std::string> convert(EnableString<DST> = 0) const;
220 
221  template <typename DST>
222  nonstd::expected<DST, std::string> convert(EnableArithmetic<DST> = nullptr) const;
223 
224  template <typename DST>
225  nonstd::expected<DST, std::string> convert(EnableEnum<DST> = 0) const;
226 
227  template <typename DST>
228  nonstd::expected<DST, std::string> convert(EnableUnknownType<DST> = 0) const
229  {
230  return nonstd::make_unexpected(errorMsg<DST>());
231  }
232 
233  template <typename T>
234  std::string errorMsg() const
235  {
236  return StrCat("[Any::convert]: no known safe conversion between [", demangle(type()),
237  "] and [", demangle(typeid(T)), "]");
238  }
239 };
240 
241 //-------------------------------------------------------------
242 //-------------------------------------------------------------
243 //-------------------------------------------------------------
244 
245 template <typename SRC, typename TO>
246 inline bool ValidCast(const SRC& val)
247 {
248  return (val == static_cast<SRC>(static_cast<TO>(val)));
249 }
250 
251 template <typename T>
252 inline bool isCastingSafe(const std::type_index& type, const T& val)
253 {
254  if(type == typeid(T))
255  {
256  return true;
257  }
258 
259  if(std::type_index(typeid(uint8_t)) == type)
260  {
261  return ValidCast<T, uint8_t>(val);
262  }
263  if(std::type_index(typeid(uint16_t)) == type)
264  {
265  return ValidCast<T, uint16_t>(val);
266  }
267  if(std::type_index(typeid(uint32_t)) == type)
268  {
269  return ValidCast<T, uint32_t>(val);
270  }
271  if(std::type_index(typeid(uint64_t)) == type)
272  {
273  return ValidCast<T, uint64_t>(val);
274  }
275  //------------
276  if(std::type_index(typeid(int8_t)) == type)
277  {
278  return ValidCast<T, int8_t>(val);
279  }
280  if(std::type_index(typeid(int16_t)) == type)
281  {
282  return ValidCast<T, int16_t>(val);
283  }
284  if(std::type_index(typeid(int32_t)) == type)
285  {
286  return ValidCast<T, int32_t>(val);
287  }
288  if(std::type_index(typeid(int64_t)) == type)
289  {
290  return ValidCast<T, int64_t>(val);
291  }
292  //------------
293  if(std::type_index(typeid(float)) == type)
294  {
295  return ValidCast<T, float>(val);
296  }
297  if(std::type_index(typeid(double)) == type)
298  {
299  return ValidCast<T, double>(val);
300  }
301  return false;
302 }
303 
304 inline Any& Any::operator=(const Any& other)
305 {
306  this->_any = other._any;
307  this->_original_type = other._original_type;
308  return *this;
309 }
310 
311 inline bool Any::isNumber() const
312 {
313  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t) ||
314  _any.type() == typeid(double);
315 }
316 
317 inline bool Any::isIntegral() const
318 {
319  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t);
320 }
321 
322 inline void Any::copyInto(Any& dst)
323 {
324  if(dst.empty())
325  {
326  dst = *this;
327  return;
328  }
329 
330  const auto& dst_type = dst.castedType();
331 
332  if((castedType() == dst_type) || (isString() && dst.isString()))
333  {
334  dst._any = _any;
335  }
336  else if(isNumber() && dst.isNumber())
337  {
338  if(dst_type == typeid(int64_t))
339  {
340  dst._any = cast<int64_t>();
341  }
342  else if(dst_type == typeid(uint64_t))
343  {
344  dst._any = cast<uint64_t>();
345  }
346  else if(dst_type == typeid(double))
347  {
348  dst._any = cast<double>();
349  }
350  else
351  {
352  throw std::runtime_error("Any::copyInto fails");
353  }
354  }
355  else
356  {
357  throw std::runtime_error("Any::copyInto fails");
358  }
359 }
360 
361 template <typename DST>
362 inline nonstd::expected<DST, std::string> Any::convert(EnableString<DST>) const
363 {
364  const auto& type = _any.type();
365 
366  if(type == typeid(SafeAny::SimpleString))
367  {
368  return linb::any_cast<SafeAny::SimpleString>(_any).toStdString();
369  }
370  else if(type == typeid(int64_t))
371  {
372  return std::to_string(linb::any_cast<int64_t>(_any));
373  }
374  else if(type == typeid(uint64_t))
375  {
376  return std::to_string(linb::any_cast<uint64_t>(_any));
377  }
378  else if(type == typeid(double))
379  {
380  return std::to_string(linb::any_cast<double>(_any));
381  }
382 
383  return nonstd::make_unexpected(errorMsg<DST>());
384 }
385 
386 template <typename T>
387 inline nonstd::expected<T, std::string> Any::stringToNumber() const
388 {
389  static_assert(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, "Expecting a "
390  "numeric type");
391 
392  const auto str = linb::any_cast<SafeAny::SimpleString>(_any);
393 #if __cpp_lib_to_chars >= 201611L
394  T out;
395  auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), out);
396  if(err == std::errc())
397  {
398  return out;
399  }
400  else
401  {
402  return nonstd::make_unexpected("Any failed string to number conversion");
403  }
404 #else
405  try
406  {
407  if constexpr(std::is_same_v<T, uint16_t>)
408  {
409  return std::stoul(str.toStdString());
410  }
411  if constexpr(std::is_integral_v<T>)
412  {
413  const int64_t val = std::stol(str.toStdString());
414  Any temp_any(val);
415  return temp_any.convert<T>();
416  }
417  if constexpr(std::is_floating_point_v<T>)
418  {
419  return std::stod(str.toStdString());
420  }
421  }
422  catch(...)
423  {
424  return nonstd::make_unexpected("Any failed string to number conversion");
425  }
426 #endif
427  return nonstd::make_unexpected("Any conversion from string failed");
428 }
429 
430 template <typename DST>
431 inline nonstd::expected<DST, std::string> Any::convert(EnableEnum<DST>) const
432 {
434 
435  const auto& type = _any.type();
436 
437  if(type == typeid(int64_t))
438  {
439  auto out = linb::any_cast<int64_t>(_any);
440  return static_cast<DST>(out);
441  }
442  else if(type == typeid(uint64_t))
443  {
444  auto out = linb::any_cast<uint64_t>(_any);
445  return static_cast<DST>(out);
446  }
447 
448  return nonstd::make_unexpected(errorMsg<DST>());
449 }
450 
451 template <typename DST>
452 inline nonstd::expected<DST, std::string> Any::convert(EnableArithmetic<DST>) const
453 {
455  DST out;
456 
457  const auto& type = _any.type();
458 
459  if(type == typeid(int64_t))
460  {
461  convertNumber<int64_t, DST>(linb::any_cast<int64_t>(_any), out);
462  }
463  else if(type == typeid(uint64_t))
464  {
465  convertNumber<uint64_t, DST>(linb::any_cast<uint64_t>(_any), out);
466  }
467  else if(type == typeid(double))
468  {
469  convertNumber<double, DST>(linb::any_cast<double>(_any), out);
470  }
471  else
472  {
473  return nonstd::make_unexpected(errorMsg<DST>());
474  }
475  return out;
476 }
477 
478 template <typename T>
479 inline nonstd::expected<T, std::string> Any::tryCast() const
480 {
481  static_assert(!std::is_reference<T>::value, "Any::cast uses value semantic, "
482  "can not cast to reference");
483 
484  if(_any.empty())
485  {
486  throw std::runtime_error("Any::cast failed because it is empty");
487  }
488 
489  if(castedType() == typeid(T))
490  {
491  return linb::any_cast<T>(_any);
492  }
493 
494  // special case when the output is an enum.
495  // We will try first a int convertion
496  if constexpr(std::is_enum_v<T>)
497  {
498  if(isNumber())
499  {
500  return static_cast<T>(convert<int>().value());
501  }
502  if(isString())
503  {
504  if(auto out = stringToNumber<int64_t>())
505  {
506  return static_cast<T>(out.value());
507  }
508  }
509  return nonstd::make_unexpected("Any::cast failed to cast to enum type");
510  }
511 
512  if(isString())
513  {
514  if constexpr(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
515  {
516  if(auto out = stringToNumber<T>())
517  {
518  return out.value();
519  }
520  else
521  {
522  return out;
523  }
524  }
525  }
526 
527  if(auto res = convert<T>())
528  {
529  return res.value();
530  }
531  else
532  {
533  return res;
534  }
535 }
536 
537 } // end namespace BT
BT
Definition: ex01_wrap_legacy.cpp:29
SafeAny
Definition: convert_impl.hpp:21
BT::demangle
std::string demangle(char const *name)
Definition: demangle_util.h:74
BT::Any::EnableEnum
typename std::enable_if< std::is_enum< T >::value >::type * EnableEnum
Definition: safe_any.hpp:54
BT::Any
Definition: safe_any.hpp:36
BT::Any::isString
bool isString() const
Definition: safe_any.hpp:124
BT::Any::castedType
const std::type_info & castedType() const noexcept
Definition: safe_any.hpp:202
BT::Any::Any
Any()
Definition: safe_any.hpp:65
BT::Any::~Any
~Any()=default
BT::Any::tryCast
nonstd::expected< T, std::string > tryCast() const
Definition: safe_any.hpp:479
BT::Any::isType
bool isType() const
Definition: safe_any.hpp:131
BT::Any::operator=
Any & operator=(const Any &other)
Definition: safe_any.hpp:304
BT::Any::Any
Any(const std::string &str)
Definition: safe_any.hpp:85
linb::any::empty
bool empty() const noexcept
Returns true if *this has no contained object, otherwise false.
Definition: include/behaviortree_cpp/contrib/any.hpp:167
BT::Any::EnableArithmetic
typename std::enable_if< std::is_arithmetic< T >::value >::type * EnableArithmetic
Definition: safe_any.hpp:51
strcat.hpp
BT::Any::Any
Any(const Any &other)
Definition: safe_any.hpp:70
linb::any::type
const std::type_info & type() const noexcept
If *this has a contained object of type T, typeid(T); otherwise typeid(void).
Definition: include/behaviortree_cpp/contrib/any.hpp:174
SafeAny::details::convertNumber
void convertNumber(const SRC &source, DST &target)
Definition: convert_impl.hpp:100
BT::Any::Any
Any(const T &value, EnableIntegral< T >=0)
Definition: safe_any.hpp:103
BT::Any::Any
Any(const T &value, EnableNonIntegral< T >=0)
Definition: safe_any.hpp:112
BT::isCastingSafe
bool isCastingSafe(const std::type_index &type, const T &val)
Definition: safe_any.hpp:252
BT::Any::isNumber
bool isNumber() const
Definition: safe_any.hpp:311
BT::Any::type
const std::type_index & type() const noexcept
Definition: safe_any.hpp:196
magic_enum::detail::value
constexpr E value(std::size_t i) noexcept
Definition: magic_enum.hpp:664
BT::UndefinedAnyType
static std::type_index UndefinedAnyType
Definition: safe_any.hpp:32
BT::Any::_original_type
std::type_index _original_type
Definition: safe_any.hpp:214
BT::Any::castPtr
T * castPtr()
Definition: safe_any.hpp:162
BT::Any::Any
Any(const char *str)
Definition: safe_any.hpp:89
any.hpp
BT::Ast::SimpleString
SafeAny::SimpleString SimpleString
Definition: operators.hpp:26
BT::Any::EnableString
typename std::enable_if< std::is_same< T, std::string >::value >::type * EnableString
Definition: safe_any.hpp:48
BT::Any::Any
Any(const SafeAny::SimpleString &str)
Definition: safe_any.hpp:93
BT::Any::empty
bool empty() const noexcept
Definition: safe_any.hpp:207
BT::Any::errorMsg
std::string errorMsg() const
Definition: safe_any.hpp:234
BT::Any::Any
Any(const std::type_index &type)
Definition: safe_any.hpp:107
BT::Any::Any
Any(const uint64_t &value)
Definition: safe_any.hpp:79
SafeAny::SimpleString
Definition: simple_string.hpp:18
BT::Any::Any
Any(const std::string_view &str)
Definition: safe_any.hpp:97
demangle_util.h
to_string
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.hpp:24456
BT::Any::Any
Any(Any &&other)
Definition: safe_any.hpp:73
BT::Any::cast
T cast() const
Definition: safe_any.hpp:146
BT::StrCat
std::string StrCat()
Definition: strcat.hpp:46
BT::Any::Any
Any(const double &value)
Definition: safe_any.hpp:76
convert_impl.hpp
BT::Any::EnableIntegral
typename std::enable_if< std::is_integral< T >::value||std::is_enum< T >::value >::type * EnableIntegral
Definition: safe_any.hpp:40
linb::any
Definition: include/behaviortree_cpp/contrib/any.hpp:66
BT::Any::EnableUnknownType
typename std::enable_if<!std::is_arithmetic< T >::value &&!std::is_enum< T >::value &&!std::is_same< T, std::string >::value >::type * EnableUnknownType
Definition: safe_any.hpp:59
BT::Any::convert
nonstd::expected< DST, std::string > convert(EnableUnknownType< DST >=0) const
Definition: safe_any.hpp:228
std
Definition: std.hpp:30
BT::Any::isIntegral
bool isIntegral() const
Definition: safe_any.hpp:317
BT::ValidCast
bool ValidCast(const SRC &val)
Definition: safe_any.hpp:246
BT::Any::convert
nonstd::expected< DST, std::string > convert(EnableString< DST >=0) const
Definition: safe_any.hpp:362
BT::Any::copyInto
void copyInto(Any &dst)
Definition: safe_any.hpp:322
BT::Any::_any
linb::any _any
Definition: safe_any.hpp:213
expected.hpp
BT::Any::stringToNumber
nonstd::expected< T, std::string > stringToNumber() const
Definition: safe_any.hpp:387
BT::Any::EnableNonIntegral
typename std::enable_if<!std::is_integral< T >::value &&!std::is_enum< T >::value >::type * EnableNonIntegral
Definition: safe_any.hpp:44
lexy::_detail::string_view
basic_string_view< char > string_view
Definition: string_view.hpp:184
BT::Any::Any
Any(const float &value)
Definition: safe_any.hpp:82


behaviortree_cpp_v4
Author(s): Davide Faconti
autogenerated on Fri Jun 28 2024 02:20:08