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  // First check numeric limits
249  if constexpr(std::is_arithmetic_v<SRC> && std::is_arithmetic_v<TO>)
250  {
251  // Handle conversion to floating point
252  if constexpr(std::is_floating_point_v<TO>)
253  {
254  if constexpr(std::is_integral_v<SRC>)
255  {
256  // For integral to float, check if we can represent the value exactly
257  TO as_float = static_cast<TO>(val);
258  SRC back_conv = static_cast<SRC>(as_float);
259  return back_conv == val;
260  }
261  }
262  // Handle conversion to integral
263  else if constexpr(std::is_integral_v<TO>)
264  {
265  if(val > static_cast<SRC>(std::numeric_limits<TO>::max()) ||
266  val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
267  {
268  return false;
269  }
270  }
271  }
272 
273  TO as_target = static_cast<TO>(val);
274  SRC back_to_source = static_cast<SRC>(as_target);
275  return val == back_to_source;
276 }
277 
278 template <typename T>
279 inline bool isCastingSafe(const std::type_index& type, const T& val)
280 {
281  if(type == typeid(T))
282  {
283  return true;
284  }
285 
286  if(std::type_index(typeid(uint8_t)) == type)
287  {
288  return ValidCast<T, uint8_t>(val);
289  }
290  if(std::type_index(typeid(uint16_t)) == type)
291  {
292  return ValidCast<T, uint16_t>(val);
293  }
294  if(std::type_index(typeid(uint32_t)) == type)
295  {
296  return ValidCast<T, uint32_t>(val);
297  }
298  if(std::type_index(typeid(uint64_t)) == type)
299  {
300  return ValidCast<T, uint64_t>(val);
301  }
302  //------------
303  if(std::type_index(typeid(int8_t)) == type)
304  {
305  return ValidCast<T, int8_t>(val);
306  }
307  if(std::type_index(typeid(int16_t)) == type)
308  {
309  return ValidCast<T, int16_t>(val);
310  }
311  if(std::type_index(typeid(int32_t)) == type)
312  {
313  return ValidCast<T, int32_t>(val);
314  }
315  if(std::type_index(typeid(int64_t)) == type)
316  {
317  return ValidCast<T, int64_t>(val);
318  }
319  //------------
320  if(std::type_index(typeid(float)) == type)
321  {
322  return ValidCast<T, float>(val);
323  }
324  if(std::type_index(typeid(double)) == type)
325  {
326  return ValidCast<T, double>(val);
327  }
328  return false;
329 }
330 
331 inline Any& Any::operator=(const Any& other)
332 {
333  this->_any = other._any;
334  this->_original_type = other._original_type;
335  return *this;
336 }
337 
338 inline bool Any::isNumber() const
339 {
340  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t) ||
341  _any.type() == typeid(double);
342 }
343 
344 inline bool Any::isIntegral() const
345 {
346  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t);
347 }
348 
349 inline void Any::copyInto(Any& dst)
350 {
351  if(dst.empty())
352  {
353  dst = *this;
354  return;
355  }
356 
357  const auto& dst_type = dst.castedType();
358 
359  if((castedType() == dst_type) || (isString() && dst.isString()))
360  {
361  dst._any = _any;
362  }
363  else if(isNumber() && dst.isNumber())
364  {
365  if(dst_type == typeid(int64_t))
366  {
367  dst._any = cast<int64_t>();
368  }
369  else if(dst_type == typeid(uint64_t))
370  {
371  dst._any = cast<uint64_t>();
372  }
373  else if(dst_type == typeid(double))
374  {
375  dst._any = cast<double>();
376  }
377  else
378  {
379  throw std::runtime_error("Any::copyInto fails");
380  }
381  }
382  else
383  {
384  throw std::runtime_error("Any::copyInto fails");
385  }
386 }
387 
388 template <typename DST>
389 inline nonstd::expected<DST, std::string> Any::convert(EnableString<DST>) const
390 {
391  const auto& type = _any.type();
392 
393  if(type == typeid(SafeAny::SimpleString))
394  {
395  return linb::any_cast<SafeAny::SimpleString>(_any).toStdString();
396  }
397  else if(type == typeid(int64_t))
398  {
399  return std::to_string(linb::any_cast<int64_t>(_any));
400  }
401  else if(type == typeid(uint64_t))
402  {
403  return std::to_string(linb::any_cast<uint64_t>(_any));
404  }
405  else if(type == typeid(double))
406  {
407  return std::to_string(linb::any_cast<double>(_any));
408  }
409 
410  return nonstd::make_unexpected(errorMsg<DST>());
411 }
412 
413 template <typename T>
414 inline nonstd::expected<T, std::string> Any::stringToNumber() const
415 {
416  static_assert(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, "Expecting a "
417  "numeric type");
418 
419  const auto str = linb::any_cast<SafeAny::SimpleString>(_any);
420 #if __cpp_lib_to_chars >= 201611L
421  T out;
422  auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), out);
423  if(err == std::errc())
424  {
425  return out;
426  }
427  else
428  {
429  return nonstd::make_unexpected("Any failed string to number conversion");
430  }
431 #else
432  try
433  {
434  if constexpr(std::is_same_v<T, uint16_t>)
435  {
436  return std::stoul(str.toStdString());
437  }
438  if constexpr(std::is_integral_v<T>)
439  {
440  const int64_t val = std::stol(str.toStdString());
441  Any temp_any(val);
442  return temp_any.convert<T>();
443  }
444  if constexpr(std::is_floating_point_v<T>)
445  {
446  return std::stod(str.toStdString());
447  }
448  }
449  catch(...)
450  {
451  return nonstd::make_unexpected("Any failed string to number conversion");
452  }
453 #endif
454  return nonstd::make_unexpected("Any conversion from string failed");
455 }
456 
457 template <typename DST>
458 inline nonstd::expected<DST, std::string> Any::convert(EnableEnum<DST>) const
459 {
461 
462  const auto& type = _any.type();
463 
464  if(type == typeid(int64_t))
465  {
466  auto out = linb::any_cast<int64_t>(_any);
467  return static_cast<DST>(out);
468  }
469  else if(type == typeid(uint64_t))
470  {
471  auto out = linb::any_cast<uint64_t>(_any);
472  return static_cast<DST>(out);
473  }
474 
475  return nonstd::make_unexpected(errorMsg<DST>());
476 }
477 
478 template <typename DST>
479 inline nonstd::expected<DST, std::string> Any::convert(EnableArithmetic<DST>) const
480 {
482  DST out;
483 
484  const auto& type = _any.type();
485 
486  if(type == typeid(int64_t))
487  {
488  convertNumber<int64_t, DST>(linb::any_cast<int64_t>(_any), out);
489  }
490  else if(type == typeid(uint64_t))
491  {
492  convertNumber<uint64_t, DST>(linb::any_cast<uint64_t>(_any), out);
493  }
494  else if(type == typeid(double))
495  {
496  convertNumber<double, DST>(linb::any_cast<double>(_any), out);
497  }
498  else
499  {
500  return nonstd::make_unexpected(errorMsg<DST>());
501  }
502  return out;
503 }
504 
505 template <typename T>
506 inline nonstd::expected<T, std::string> Any::tryCast() const
507 {
508  static_assert(!std::is_reference<T>::value, "Any::cast uses value semantic, "
509  "can not cast to reference");
510 
511  if(_any.empty())
512  {
513  throw std::runtime_error("Any::cast failed because it is empty");
514  }
515 
516  if(castedType() == typeid(T))
517  {
518  return linb::any_cast<T>(_any);
519  }
520 
521  // special case when the output is an enum.
522  // We will try first a int convertion
523  if constexpr(std::is_enum_v<T>)
524  {
525  if(isNumber())
526  {
527  return static_cast<T>(convert<int>().value());
528  }
529  if(isString())
530  {
531  if(auto out = stringToNumber<int64_t>())
532  {
533  return static_cast<T>(out.value());
534  }
535  }
536  return nonstd::make_unexpected("Any::cast failed to cast to enum type");
537  }
538 
539  if(isString())
540  {
541  if constexpr(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
542  {
543  if(auto out = stringToNumber<T>())
544  {
545  return out.value();
546  }
547  else
548  {
549  return out;
550  }
551  }
552  }
553 
554  if(auto res = convert<T>())
555  {
556  return res.value();
557  }
558  else
559  {
560  return res;
561  }
562 }
563 
564 } // end namespace BT
BT
Definition: ex01_wrap_legacy.cpp:29
SafeAny
Definition: convert_impl.hpp:22
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:506
BT::Any::isType
bool isType() const
Definition: safe_any.hpp:131
BT::Any::operator=
Any & operator=(const Any &other)
Definition: safe_any.hpp:331
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:143
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:279
BT::Any::isNumber
bool isNumber() const
Definition: safe_any.hpp:338
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:31
BT::Any::isIntegral
bool isIntegral() const
Definition: safe_any.hpp:344
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:389
BT::Any::copyInto
void copyInto(Any &dst)
Definition: safe_any.hpp:349
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:414
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:192
BT::Any::Any
Any(const float &value)
Definition: safe_any.hpp:82


behaviortree_cpp_v4
Author(s): Davide Faconti
autogenerated on Wed Apr 16 2025 02:20:57