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 // static_cast<SRC>(TO::max) rounds up to an out-of-range value when TO has
263 // more significant bits than a floating SRC's mantissa (e.g. double ->
264 // int64_t). A plain `>` would let that rounded boundary through and the
265 // cast to TO below overflows, so use a half-open upper bound in that case.
266 constexpr bool max_rounds_up =
267 std::is_floating_point_v<SRC> &&
268 std::numeric_limits<TO>::digits > std::numeric_limits<SRC>::digits;
269 const SRC max_limit = static_cast<SRC>(std::numeric_limits<TO>::max());
270 const bool over = max_rounds_up ? (val >= max_limit) : (val > max_limit);
271 if(over || val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
272 {
273 return false;
274 }
275 }
276 }
277
278 TO as_target = static_cast<TO>(val);
279 SRC back_to_source = static_cast<SRC>(as_target);
280 return val == back_to_source;
281}
282
283template <typename T>
284inline bool isCastingSafe(const std::type_index& type, const T& val)
285{
286 if(type == typeid(T))
287 {
288 return true;
289 }
290
291 if(std::type_index(typeid(uint8_t)) == type)
292 {
293 return ValidCast<T, uint8_t>(val);
294 }
295 if(std::type_index(typeid(uint16_t)) == type)
296 {
297 return ValidCast<T, uint16_t>(val);
298 }
299 if(std::type_index(typeid(uint32_t)) == type)
300 {
301 return ValidCast<T, uint32_t>(val);
302 }
303 if(std::type_index(typeid(uint64_t)) == type)
304 {
305 return ValidCast<T, uint64_t>(val);
306 }
307 //------------
308 if(std::type_index(typeid(int8_t)) == type)
309 {
310 return ValidCast<T, int8_t>(val);
311 }
312 if(std::type_index(typeid(int16_t)) == type)
313 {
314 return ValidCast<T, int16_t>(val);
315 }
316 if(std::type_index(typeid(int32_t)) == type)
317 {
318 return ValidCast<T, int32_t>(val);
319 }
320 if(std::type_index(typeid(int64_t)) == type)
321 {
322 return ValidCast<T, int64_t>(val);
323 }
324 //------------
325 if(std::type_index(typeid(float)) == type)
326 {
327 return ValidCast<T, float>(val);
328 }
329 if(std::type_index(typeid(double)) == type)
330 {
331 return ValidCast<T, double>(val);
332 }
333 return false;
334}
335
336inline Any& Any::operator=(const Any& other)
337{
338 if(this != &other)
339 {
340 this->_any = other._any;
341 this->_original_type = other._original_type;
342 }
343 return *this;
344}
345
346inline Any& Any::operator=(Any&& other) noexcept
347{
348 this->_any = std::move(other._any);
349 this->_original_type = other._original_type;
350 return *this;
351}
352
353inline bool Any::isNumber() const
354{
355 return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t) ||
356 _any.type() == typeid(double);
357}
358
359inline bool Any::isIntegral() const
360{
361 return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t);
362}
363
364inline void Any::copyInto(Any& dst) const
365{
366 if(dst.empty())
367 {
368 dst = *this;
369 return;
370 }
371
372 const auto& dst_type = dst.castedType();
373
374 if((castedType() == dst_type) || (isString() && dst.isString()))
375 {
376 dst._any = _any;
377 }
378 else if(isNumber() && dst.isNumber())
379 {
380 if(dst_type == typeid(int64_t))
381 {
382 dst._any = cast<int64_t>();
383 }
384 else if(dst_type == typeid(uint64_t))
385 {
386 dst._any = cast<uint64_t>();
387 }
388 else if(dst_type == typeid(double))
389 {
390 dst._any = cast<double>();
391 }
392 else
393 {
394 throw std::runtime_error("Any::copyInto fails");
395 }
396 }
397 else
398 {
399 throw std::runtime_error("Any::copyInto fails");
400 }
401}
402
403template <typename DST>
404inline nonstd::expected<DST, std::string> Any::convert(EnableString<DST>) const
405{
406 const auto& type = _any.type();
407
408 if(type == typeid(SafeAny::SimpleString))
409 {
410 return linb::any_cast<SafeAny::SimpleString>(_any).toStdString();
411 }
412 else if(type == typeid(int64_t))
413 {
414 return std::to_string(linb::any_cast<int64_t>(_any));
415 }
416 else if(type == typeid(uint64_t))
417 {
418 return std::to_string(linb::any_cast<uint64_t>(_any));
419 }
420 else if(type == typeid(double))
421 {
422 return std::to_string(linb::any_cast<double>(_any));
423 }
424
425 return nonstd::make_unexpected(errorMsg<DST>());
426}
427
428template <typename T>
429inline nonstd::expected<T, std::string> Any::stringToNumber() const
430{
431 static_assert(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, "Expecting a "
432 "numeric type");
433
434 const auto str = linb::any_cast<SafeAny::SimpleString>(_any);
435#if __cpp_lib_to_chars >= 201611L
436 T out;
437 auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), out);
438 if(err == std::errc())
439 {
440 return out;
441 }
442 else
443 {
444 return nonstd::make_unexpected("Any failed string to number conversion");
445 }
446#else
447 try
448 {
449 if constexpr(std::is_same_v<T, uint16_t>)
450 {
451 return std::stoul(str.toStdString());
452 }
453 if constexpr(std::is_integral_v<T>)
454 {
455 const int64_t val = std::stol(str.toStdString());
456 Any temp_any(val);
457 return temp_any.convert<T>();
458 }
459 if constexpr(std::is_floating_point_v<T>)
460 {
461 return std::stod(str.toStdString());
462 }
463 }
464 catch(...)
465 {
466 return nonstd::make_unexpected("Any failed string to number conversion");
467 }
468#endif
469 return nonstd::make_unexpected("Any conversion from string failed");
470}
471
472template <typename DST>
473inline nonstd::expected<DST, std::string> Any::convert(EnableEnum<DST>) const
474{
475 using SafeAny::details::convertNumber;
476
477 const auto& type = _any.type();
478
479 if(type == typeid(int64_t))
480 {
481 auto out = linb::any_cast<int64_t>(_any);
482 return static_cast<DST>(out);
483 }
484 else if(type == typeid(uint64_t))
485 {
486 auto out = linb::any_cast<uint64_t>(_any);
487 return static_cast<DST>(out);
488 }
489
490 return nonstd::make_unexpected(errorMsg<DST>());
491}
492
493template <typename DST>
494inline nonstd::expected<DST, std::string> Any::convert(EnableArithmetic<DST>) const
495{
496 using SafeAny::details::convertNumber;
497 DST out;
498
499 const auto& type = _any.type();
500
501 if(type == typeid(int64_t))
502 {
503 convertNumber<int64_t, DST>(linb::any_cast<int64_t>(_any), out);
504 }
505 else if(type == typeid(uint64_t))
506 {
507 convertNumber<uint64_t, DST>(linb::any_cast<uint64_t>(_any), out);
508 }
509 else if(type == typeid(double))
510 {
511 convertNumber<double, DST>(linb::any_cast<double>(_any), out);
512 }
513 else
514 {
515 return nonstd::make_unexpected(errorMsg<DST>());
516 }
517 return out;
518}
519
520template <typename T>
521inline nonstd::expected<T, std::string> Any::tryCast() const
522{
523 static_assert(!std::is_reference<T>::value, "Any::cast uses value semantic, "
524 "can not cast to reference");
525
526 if(_any.empty())
527 {
528 throw std::runtime_error("Any::cast failed because it is empty");
529 }
530
531 if(castedType() == typeid(T))
532 {
533 return linb::any_cast<T>(_any);
534 }
535
536 // special case when the output is an enum.
537 // We will try first a int conversion
538 if constexpr(std::is_enum_v<T>)
539 {
540 if(isNumber())
541 {
542 return static_cast<T>(convert<int>().value());
543 }
544 if(isString())
545 {
546 if(auto out = stringToNumber<int64_t>())
547 {
548 return static_cast<T>(out.value());
549 }
550 }
551 return nonstd::make_unexpected("Any::cast failed to cast to enum type");
552 }
553
554 if(isString())
555 {
556 if constexpr(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
557 {
558 if(auto out = stringToNumber<T>())
559 {
560 return out.value();
561 }
562 else
563 {
564 return out;
565 }
566 }
567 }
568
569 if(auto res = convert<T>())
570 {
571 return std::move(res.value());
572 }
573 else
574 {
575 return res;
576 }
577}
578
579template <typename T>
580inline nonstd::expected<T, std::string>
581Any::tryCastWithRegistry(const PolymorphicCastRegistry& registry) const
582{
583 static_assert(is_shared_ptr<T>::value, "tryCastWithRegistry only works with shared_ptr "
584 "types");
585
586 if(_any.empty())
587 {
588 return nonstd::make_unexpected("Any::tryCastWithRegistry failed: empty value");
589 }
590
591 // Try to cast using the registry
592 auto result = registry.tryCast(_any, _original_type, typeid(T));
593 if(result)
594 {
595 try
596 {
597 return linb::any_cast<T>(result.value());
598 }
599 catch(const std::exception& e)
600 {
601 return nonstd::make_unexpected(StrCat("Polymorphic cast failed: ", e.what()));
602 }
603 }
604
605 return nonstd::make_unexpected(StrCat("[Any::tryCastWithRegistry]: ", result.error(),
606 " (from [", demangle(_original_type), "] to [",
607 demangle(typeid(T)), "])"));
608}
609
610} // 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