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