BehaviorTree
Core Library to create and execute Behavior Trees
Loading...
Searching...
No Matches
safe_any.hpp
1/* Copyright (C) 2022-2025 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 "behaviortree_cpp/contrib/any.hpp"
20#include "behaviortree_cpp/contrib/expected.hpp"
21#include "behaviortree_cpp/utils/convert_impl.hpp"
22#include "behaviortree_cpp/utils/demangle_util.h"
23#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp"
24#include "behaviortree_cpp/utils/strcat.hpp"
25
26#include <memory>
27#include <string>
28#include <type_traits>
29#include <typeindex>
30
31namespace BT
32{
33
34static std::type_index UndefinedAnyType = typeid(nullptr);
35
36// Trait to detect std::shared_ptr types (used for polymorphic port support)
37template <typename T>
38struct is_shared_ptr : std::false_type
39{
40};
41
42template <typename U>
43struct is_shared_ptr<std::shared_ptr<U>> : std::true_type
44{
45};
46
47// Rational: since type erased numbers will always use at least 8 bytes
48// it is faster to cast everything to either double, uint64_t or int64_t.
49class Any
50{
51 template <typename T>
52 using EnableIntegral = typename std::enable_if<std::is_integral<T>::value ||
53 std::is_enum<T>::value>::type*;
54
55 template <typename T>
56 using EnableNonIntegral = typename std::enable_if<!std::is_integral<T>::value &&
57 !std::is_enum<T>::value>::type*;
58
59 template <typename T>
60 using EnableString =
61 typename std::enable_if<std::is_same<T, std::string>::value>::type*;
62
63 template <typename T>
64 using EnableArithmetic = typename std::enable_if<std::is_arithmetic<T>::value>::type*;
65
66 template <typename T>
67 using EnableEnum = typename std::enable_if<std::is_enum<T>::value>::type*;
68
69 template <typename T>
70 using EnableUnknownType =
71 typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_enum<T>::value &&
72 !std::is_same<T, std::string>::value>::type*;
73
74 template <typename T>
75 nonstd::expected<T, std::string> stringToNumber() const;
76
77public:
78 Any() : _original_type(UndefinedAnyType)
79 {}
80
81 ~Any() = default;
82
83 Any(const Any& other) : _any(other._any), _original_type(other._original_type)
84 {}
85
86 Any(Any&& other) noexcept
87 : _any(std::move(other._any)), _original_type(other._original_type)
88 {}
89
90 explicit Any(const double& value) : _any(value), _original_type(typeid(double))
91 {}
92
93 explicit Any(const uint64_t& value) : _any(value), _original_type(typeid(uint64_t))
94 {}
95
96 explicit Any(const float& value) : _any(double(value)), _original_type(typeid(float))
97 {}
98
99 explicit Any(const std::string& str)
100 : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
101 {}
102
103 explicit Any(const char* str)
104 : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
105 {}
106
107 explicit Any(const SafeAny::SimpleString& str)
108 : _any(str), _original_type(typeid(std::string))
109 {}
110
111 explicit Any(const std::string_view& str)
112 : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
113 {}
114
115 // all the other integrals are casted to int64_t
116 template <typename T>
117 explicit Any(const T& value, EnableIntegral<T> = 0)
118 : _any(int64_t(value)), _original_type(typeid(T))
119 {}
120
121 Any(const std::type_index& type) : _original_type(type)
122 {}
123
124 // default for other custom types
125 template <typename T>
126 explicit Any(const T& value, EnableNonIntegral<T> = 0)
127 : _any(value), _original_type(typeid(T))
128 {
129 static_assert(!std::is_reference<T>::value, "Any can not contain references");
130 }
131
132 Any& operator=(const Any& other);
133
134 Any& operator=(Any&& other) noexcept;
135
136 [[nodiscard]] bool isNumber() const;
137
138 [[nodiscard]] bool isIntegral() const;
139
140 [[nodiscard]] bool isString() const
141 {
142 return _any.type() == typeid(SafeAny::SimpleString);
143 }
144
145 // check is the original type is equal to T
146 template <typename T>
147 [[nodiscard]] bool isType() const
148 {
149 return _original_type == typeid(T);
150 }
151
152 // copy the value (casting into dst). We preserve the destination type.
153 void copyInto(Any& dst) const;
154
155 // this is different from any_cast, because if allows safe
156 // conversions between arithmetic values and from/to string.
157 template <typename T>
158 nonstd::expected<T, std::string> tryCast() const;
159
160 // tryCast with polymorphic registry support (Issue #943)
161 // Attempts polymorphic cast for shared_ptr types using the provided registry.
162 template <typename T>
163 nonstd::expected<T, std::string>
164 tryCastWithRegistry(const PolymorphicCastRegistry& registry) const;
165
166 // same as tryCast, but throws if fails
167 template <typename T>
168 [[nodiscard]] T cast() const
169 {
170 if(auto res = tryCast<T>())
171 {
172 return res.value();
173 }
174 else
175 {
176 throw std::runtime_error(res.error());
177 }
178 }
179
180 // Method to access the value by pointer.
181 // It will return nullptr, if the user try to cast it to a
182 // wrong type or if Any was empty.
183 template <typename T>
184 [[nodiscard]] T* castPtr()
185 {
186 static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
187 "[double]. Use that instead");
188
189 return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
190 }
191
192 // This is the original type
193 [[nodiscard]] const std::type_index& type() const noexcept
194 {
195 return _original_type;
196 }
197
198 // This is the type we casted to, internally
199 [[nodiscard]] const std::type_info& castedType() const noexcept
200 {
201 return _any.type();
202 }
203
204 [[nodiscard]] bool empty() const noexcept
205 {
206 return _any.empty();
207 }
208
209private:
210 linb::any _any;
211 std::type_index _original_type;
212
213 //----------------------------
214
215 template <typename DST>
216 nonstd::expected<DST, std::string> convert(EnableString<DST> = 0) const;
217
218 template <typename DST>
219 nonstd::expected<DST, std::string> convert(EnableArithmetic<DST> = nullptr) const;
220
221 template <typename DST>
222 nonstd::expected<DST, std::string> convert(EnableEnum<DST> = 0) const;
223
224 template <typename DST>
225 nonstd::expected<DST, std::string> convert(EnableUnknownType<DST> = 0) const
226 {
227 return nonstd::make_unexpected(errorMsg<DST>());
228 }
229
230 template <typename T>
231 std::string errorMsg() const
232 {
233 return StrCat("[Any::convert]: no known safe conversion between [", demangle(type()),
234 "] and [", demangle(typeid(T)), "]");
235 }
236};
237
238//-------------------------------------------------------------
239//-------------------------------------------------------------
240//-------------------------------------------------------------
241
242template <typename SRC, typename TO>
243inline bool ValidCast(const SRC& val)
244{
245 // First check numeric limits
246 if constexpr(std::is_arithmetic_v<SRC> && std::is_arithmetic_v<TO>)
247 {
248 // Handle conversion to floating point
249 if constexpr(std::is_floating_point_v<TO>)
250 {
251 if constexpr(std::is_integral_v<SRC>)
252 {
253 // For integral to float, check if we can represent the value exactly
254 TO as_float = static_cast<TO>(val);
255 SRC back_conv = static_cast<SRC>(as_float);
256 return back_conv == val;
257 }
258 }
259 // Handle conversion to integral
260 else if constexpr(std::is_integral_v<TO>)
261 {
262 if(val > static_cast<SRC>(std::numeric_limits<TO>::max()) ||
263 val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
264 {
265 return false;
266 }
267 }
268 }
269
270 TO as_target = static_cast<TO>(val);
271 SRC back_to_source = static_cast<SRC>(as_target);
272 return val == back_to_source;
273}
274
275template <typename T>
276inline bool isCastingSafe(const std::type_index& type, const T& val)
277{
278 if(type == typeid(T))
279 {
280 return true;
281 }
282
283 if(std::type_index(typeid(uint8_t)) == type)
284 {
285 return ValidCast<T, uint8_t>(val);
286 }
287 if(std::type_index(typeid(uint16_t)) == type)
288 {
289 return ValidCast<T, uint16_t>(val);
290 }
291 if(std::type_index(typeid(uint32_t)) == type)
292 {
293 return ValidCast<T, uint32_t>(val);
294 }
295 if(std::type_index(typeid(uint64_t)) == type)
296 {
297 return ValidCast<T, uint64_t>(val);
298 }
299 //------------
300 if(std::type_index(typeid(int8_t)) == type)
301 {
302 return ValidCast<T, int8_t>(val);
303 }
304 if(std::type_index(typeid(int16_t)) == type)
305 {
306 return ValidCast<T, int16_t>(val);
307 }
308 if(std::type_index(typeid(int32_t)) == type)
309 {
310 return ValidCast<T, int32_t>(val);
311 }
312 if(std::type_index(typeid(int64_t)) == type)
313 {
314 return ValidCast<T, int64_t>(val);
315 }
316 //------------
317 if(std::type_index(typeid(float)) == type)
318 {
319 return ValidCast<T, float>(val);
320 }
321 if(std::type_index(typeid(double)) == type)
322 {
323 return ValidCast<T, double>(val);
324 }
325 return false;
326}
327
328inline Any& Any::operator=(const Any& other)
329{
330 if(this != &other)
331 {
332 this->_any = other._any;
333 this->_original_type = other._original_type;
334 }
335 return *this;
336}
337
338inline Any& Any::operator=(Any&& other) noexcept
339{
340 this->_any = std::move(other._any);
341 this->_original_type = other._original_type;
342 return *this;
343}
344
345inline bool Any::isNumber() const
346{
347 return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t) ||
348 _any.type() == typeid(double);
349}
350
351inline bool Any::isIntegral() const
352{
353 return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t);
354}
355
356inline void Any::copyInto(Any& dst) const
357{
358 if(dst.empty())
359 {
360 dst = *this;
361 return;
362 }
363
364 const auto& dst_type = dst.castedType();
365
366 if((castedType() == dst_type) || (isString() && dst.isString()))
367 {
368 dst._any = _any;
369 }
370 else if(isNumber() && dst.isNumber())
371 {
372 if(dst_type == typeid(int64_t))
373 {
374 dst._any = cast<int64_t>();
375 }
376 else if(dst_type == typeid(uint64_t))
377 {
378 dst._any = cast<uint64_t>();
379 }
380 else if(dst_type == typeid(double))
381 {
382 dst._any = cast<double>();
383 }
384 else
385 {
386 throw std::runtime_error("Any::copyInto fails");
387 }
388 }
389 else
390 {
391 throw std::runtime_error("Any::copyInto fails");
392 }
393}
394
395template <typename DST>
396inline nonstd::expected<DST, std::string> Any::convert(EnableString<DST>) const
397{
398 const auto& type = _any.type();
399
400 if(type == typeid(SafeAny::SimpleString))
401 {
402 return linb::any_cast<SafeAny::SimpleString>(_any).toStdString();
403 }
404 else if(type == typeid(int64_t))
405 {
406 return std::to_string(linb::any_cast<int64_t>(_any));
407 }
408 else if(type == typeid(uint64_t))
409 {
410 return std::to_string(linb::any_cast<uint64_t>(_any));
411 }
412 else if(type == typeid(double))
413 {
414 return std::to_string(linb::any_cast<double>(_any));
415 }
416
417 return nonstd::make_unexpected(errorMsg<DST>());
418}
419
420template <typename T>
421inline nonstd::expected<T, std::string> Any::stringToNumber() const
422{
423 static_assert(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, "Expecting a "
424 "numeric type");
425
426 const auto str = linb::any_cast<SafeAny::SimpleString>(_any);
427#if __cpp_lib_to_chars >= 201611L
428 T out;
429 auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), out);
430 if(err == std::errc())
431 {
432 return out;
433 }
434 else
435 {
436 return nonstd::make_unexpected("Any failed string to number conversion");
437 }
438#else
439 try
440 {
441 if constexpr(std::is_same_v<T, uint16_t>)
442 {
443 return std::stoul(str.toStdString());
444 }
445 if constexpr(std::is_integral_v<T>)
446 {
447 const int64_t val = std::stol(str.toStdString());
448 Any temp_any(val);
449 return temp_any.convert<T>();
450 }
451 if constexpr(std::is_floating_point_v<T>)
452 {
453 return std::stod(str.toStdString());
454 }
455 }
456 catch(...)
457 {
458 return nonstd::make_unexpected("Any failed string to number conversion");
459 }
460#endif
461 return nonstd::make_unexpected("Any conversion from string failed");
462}
463
464template <typename DST>
465inline nonstd::expected<DST, std::string> Any::convert(EnableEnum<DST>) const
466{
467 using SafeAny::details::convertNumber;
468
469 const auto& type = _any.type();
470
471 if(type == typeid(int64_t))
472 {
473 auto out = linb::any_cast<int64_t>(_any);
474 return static_cast<DST>(out);
475 }
476 else if(type == typeid(uint64_t))
477 {
478 auto out = linb::any_cast<uint64_t>(_any);
479 return static_cast<DST>(out);
480 }
481
482 return nonstd::make_unexpected(errorMsg<DST>());
483}
484
485template <typename DST>
486inline nonstd::expected<DST, std::string> Any::convert(EnableArithmetic<DST>) const
487{
488 using SafeAny::details::convertNumber;
489 DST out;
490
491 const auto& type = _any.type();
492
493 if(type == typeid(int64_t))
494 {
495 convertNumber<int64_t, DST>(linb::any_cast<int64_t>(_any), out);
496 }
497 else if(type == typeid(uint64_t))
498 {
499 convertNumber<uint64_t, DST>(linb::any_cast<uint64_t>(_any), out);
500 }
501 else if(type == typeid(double))
502 {
503 convertNumber<double, DST>(linb::any_cast<double>(_any), out);
504 }
505 else
506 {
507 return nonstd::make_unexpected(errorMsg<DST>());
508 }
509 return out;
510}
511
512template <typename T>
513inline nonstd::expected<T, std::string> Any::tryCast() const
514{
515 static_assert(!std::is_reference<T>::value, "Any::cast uses value semantic, "
516 "can not cast to reference");
517
518 if(_any.empty())
519 {
520 throw std::runtime_error("Any::cast failed because it is empty");
521 }
522
523 if(castedType() == typeid(T))
524 {
525 return linb::any_cast<T>(_any);
526 }
527
528 // special case when the output is an enum.
529 // We will try first a int conversion
530 if constexpr(std::is_enum_v<T>)
531 {
532 if(isNumber())
533 {
534 return static_cast<T>(convert<int>().value());
535 }
536 if(isString())
537 {
538 if(auto out = stringToNumber<int64_t>())
539 {
540 return static_cast<T>(out.value());
541 }
542 }
543 return nonstd::make_unexpected("Any::cast failed to cast to enum type");
544 }
545
546 if(isString())
547 {
548 if constexpr(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
549 {
550 if(auto out = stringToNumber<T>())
551 {
552 return out.value();
553 }
554 else
555 {
556 return out;
557 }
558 }
559 }
560
561 if(auto res = convert<T>())
562 {
563 return res.value();
564 }
565 else
566 {
567 return res;
568 }
569}
570
571template <typename T>
572inline nonstd::expected<T, std::string>
573Any::tryCastWithRegistry(const PolymorphicCastRegistry& registry) const
574{
575 static_assert(is_shared_ptr<T>::value, "tryCastWithRegistry only works with shared_ptr "
576 "types");
577
578 if(_any.empty())
579 {
580 return nonstd::make_unexpected("Any::tryCastWithRegistry failed: empty value");
581 }
582
583 // Try to cast using the registry
584 auto result = registry.tryCast(_any, _original_type, typeid(T));
585 if(result)
586 {
587 try
588 {
589 return linb::any_cast<T>(result.value());
590 }
591 catch(const std::exception& e)
592 {
593 return nonstd::make_unexpected(StrCat("Polymorphic cast failed: ", e.what()));
594 }
595 }
596
597 return nonstd::make_unexpected(StrCat("[Any::tryCastWithRegistry]: ", result.error(),
598 " (from [", demangle(_original_type), "] to [",
599 demangle(typeid(T)), "])"));
600}
601
602} // end namespace BT
Definition: safe_any.hpp:50
Registry for polymorphic shared_ptr cast relationships.
Definition: polymorphic_cast_registry.hpp:47
Definition: simple_string.hpp:19
Definition: action_node.h:24
Definition: safe_any.hpp:39