• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

BehaviorTree / BehaviorTree.CPP / 21672091355

04 Feb 2026 12:49PM UTC coverage: 80.205% (+0.4%) from 79.835%
21672091355

Pull #1107

github

web-flow
Merge 0cb314f5a into c7c0ea78a
Pull Request #1107: Add polymorphic shared_ptr port support (Issue #943)

200 of 220 new or added lines in 7 files covered. (90.91%)

2 existing lines in 1 file now uncovered.

4931 of 6148 relevant lines covered (80.2%)

20983.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.14
/include/behaviortree_cpp/utils/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/strcat.hpp"
24

25
#include <memory>
26
#include <string>
27
#include <type_traits>
28
#include <typeindex>
29

30
namespace BT
31
{
32

33
static std::type_index UndefinedAnyType = typeid(nullptr);
34

35
// Forward declaration for polymorphic cast support
36
class PolymorphicCastRegistry;
37

38
// Trait to detect std::shared_ptr types (used for polymorphic port support)
39
template <typename T>
40
struct is_shared_ptr : std::false_type
41
{
42
};
43

44
template <typename U>
45
struct is_shared_ptr<std::shared_ptr<U>> : std::true_type
46
{
47
};
48

49
// Rational: since type erased numbers will always use at least 8 bytes
50
// it is faster to cast everything to either double, uint64_t or int64_t.
51
class Any
52
{
53
  template <typename T>
54
  using EnableIntegral = typename std::enable_if<std::is_integral<T>::value ||
55
                                                 std::is_enum<T>::value>::type*;
56

57
  template <typename T>
58
  using EnableNonIntegral = typename std::enable_if<!std::is_integral<T>::value &&
59
                                                    !std::is_enum<T>::value>::type*;
60

61
  template <typename T>
62
  using EnableString =
63
      typename std::enable_if<std::is_same<T, std::string>::value>::type*;
64

65
  template <typename T>
66
  using EnableArithmetic = typename std::enable_if<std::is_arithmetic<T>::value>::type*;
67

68
  template <typename T>
69
  using EnableEnum = typename std::enable_if<std::is_enum<T>::value>::type*;
70

71
  template <typename T>
72
  using EnableUnknownType =
73
      typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_enum<T>::value &&
74
                              !std::is_same<T, std::string>::value>::type*;
75

76
  template <typename T>
77
  nonstd::expected<T, std::string> stringToNumber() const;
78

79
public:
80
  Any() : _original_type(UndefinedAnyType)
33,065✔
81
  {}
33,065✔
82

83
  ~Any() = default;
155,236✔
84

85
  Any(const Any& other) : _any(other._any), _original_type(other._original_type)
83,688✔
86
  {}
83,688✔
87

88
  Any(Any&& other) noexcept
32,696✔
89
    : _any(std::move(other._any)), _original_type(other._original_type)
32,696✔
90
  {}
32,696✔
91

92
  explicit Any(const double& value) : _any(value), _original_type(typeid(double))
465✔
93
  {}
465✔
94

95
  explicit Any(const uint64_t& value) : _any(value), _original_type(typeid(uint64_t))
96
  {}
97

98
  explicit Any(const float& value) : _any(double(value)), _original_type(typeid(float))
99
  {}
100

101
  explicit Any(const std::string& str)
140✔
102
    : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
140✔
103
  {}
140✔
104

105
  explicit Any(const char* str)
13✔
106
    : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
13✔
107
  {}
13✔
108

109
  explicit Any(const SafeAny::SimpleString& str)
110
    : _any(str), _original_type(typeid(std::string))
111
  {}
112

113
  explicit Any(const std::string_view& str)
349✔
114
    : _any(SafeAny::SimpleString(str)), _original_type(typeid(std::string))
349✔
115
  {}
349✔
116

117
  // all the other integrals are casted to int64_t
118
  template <typename T>
119
  explicit Any(const T& value, EnableIntegral<T> = 0)
4,451✔
120
    : _any(int64_t(value)), _original_type(typeid(T))
4,451✔
121
  {}
4,451✔
122

123
  Any(const std::type_index& type) : _original_type(type)
267✔
124
  {}
267✔
125

126
  // default for other custom types
127
  template <typename T>
128
  explicit Any(const T& value, EnableNonIntegral<T> = 0)
102✔
129
    : _any(value), _original_type(typeid(T))
102✔
130
  {
131
    static_assert(!std::is_reference<T>::value, "Any can not contain references");
132
  }
102✔
133

134
  Any& operator=(const Any& other);
135

136
  Any& operator=(Any&& other) noexcept;
137

138
  [[nodiscard]] bool isNumber() const;
139

140
  [[nodiscard]] bool isIntegral() const;
141

142
  [[nodiscard]] bool isString() const
2,920✔
143
  {
144
    return _any.type() == typeid(SafeAny::SimpleString);
2,920✔
145
  }
146

147
  // check is the original type is equal to T
148
  template <typename T>
149
  [[nodiscard]] bool isType() const
33✔
150
  {
151
    return _original_type == typeid(T);
33✔
152
  }
153

154
  // copy the value (casting into dst). We preserve the destination type.
155
  void copyInto(Any& dst) const;
156

157
  // this is different from any_cast, because if allows safe
158
  // conversions between arithmetic values and from/to string.
159
  template <typename T>
160
  nonstd::expected<T, std::string> tryCast() const;
161

162
  // tryCast with polymorphic registry support (Issue #943)
163
  // Attempts polymorphic cast for shared_ptr types using the provided registry.
164
  template <typename T>
165
  nonstd::expected<T, std::string>
166
  tryCastWithRegistry(const PolymorphicCastRegistry& registry) const;
167

168
  // Provide read-only access to internal any for polymorphic cast
169
  [[nodiscard]] const linb::any& internalAny() const noexcept
170
  {
171
    return _any;
172
  }
173

174
  // same as tryCast, but throws if fails
175
  template <typename T>
176
  [[nodiscard]] T cast() const
234,375✔
177
  {
178
    if(auto res = tryCast<T>())
468,743✔
179
    {
180
      return res.value();
468,718✔
181
    }
182
    else
183
    {
184
      throw std::runtime_error(res.error());
9✔
185
    }
186
  }
187

188
  // Method to access the value by pointer.
189
  // It will return nullptr, if the user try to cast it to a
190
  // wrong type or if Any was empty.
191
  template <typename T>
192
  [[nodiscard]] T* castPtr()
4✔
193
  {
194
    static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
195
                                             "[double]. Use that instead");
196

197
    return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
4✔
198
  }
199

200
  // This is the original type
201
  [[nodiscard]] const std::type_index& type() const noexcept
112✔
202
  {
203
    return _original_type;
112✔
204
  }
205

206
  // This is the type we casted to, internally
207
  [[nodiscard]] const std::type_info& castedType() const noexcept
445,838✔
208
  {
209
    return _any.type();
445,838✔
210
  }
211

212
  [[nodiscard]] bool empty() const noexcept
213,776✔
213
  {
214
    return _any.empty();
213,776✔
215
  }
216

217
private:
218
  linb::any _any;
219
  std::type_index _original_type;
220

221
  //----------------------------
222

223
  template <typename DST>
224
  nonstd::expected<DST, std::string> convert(EnableString<DST> = 0) const;
225

226
  template <typename DST>
227
  nonstd::expected<DST, std::string> convert(EnableArithmetic<DST> = nullptr) const;
228

229
  template <typename DST>
230
  nonstd::expected<DST, std::string> convert(EnableEnum<DST> = 0) const;
231

232
  template <typename DST>
233
  nonstd::expected<DST, std::string> convert(EnableUnknownType<DST> = 0) const
19✔
234
  {
235
    return nonstd::make_unexpected(errorMsg<DST>());
19✔
236
  }
237

238
  template <typename T>
239
  std::string errorMsg() const
24✔
240
  {
241
    return StrCat("[Any::convert]: no known safe conversion between [", demangle(type()),
48✔
242
                  "] and [", demangle(typeid(T)), "]");
48✔
243
  }
244
};
245

246
//-------------------------------------------------------------
247
//-------------------------------------------------------------
248
//-------------------------------------------------------------
249

250
template <typename SRC, typename TO>
251
inline bool ValidCast(const SRC& val)
6✔
252
{
253
  // First check numeric limits
254
  if constexpr(std::is_arithmetic_v<SRC> && std::is_arithmetic_v<TO>)
255
  {
256
    // Handle conversion to floating point
257
    if constexpr(std::is_floating_point_v<TO>)
258
    {
259
      if constexpr(std::is_integral_v<SRC>)
260
      {
261
        // For integral to float, check if we can represent the value exactly
262
        TO as_float = static_cast<TO>(val);
×
263
        SRC back_conv = static_cast<SRC>(as_float);
×
264
        return back_conv == val;
×
265
      }
266
    }
267
    // Handle conversion to integral
268
    else if constexpr(std::is_integral_v<TO>)
269
    {
270
      if(val > static_cast<SRC>(std::numeric_limits<TO>::max()) ||
11✔
271
         val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
5✔
272
      {
273
        return false;
3✔
274
      }
275
    }
276
  }
277

278
  TO as_target = static_cast<TO>(val);
3✔
279
  SRC back_to_source = static_cast<SRC>(as_target);
3✔
280
  return val == back_to_source;
3✔
281
}
282

283
template <typename T>
284
inline bool isCastingSafe(const std::type_index& type, const T& val)
6✔
285
{
286
  if(type == typeid(T))
6✔
287
  {
288
    return true;
×
289
  }
290

291
  if(std::type_index(typeid(uint8_t)) == type)
6✔
292
  {
293
    return ValidCast<T, uint8_t>(val);
4✔
294
  }
295
  if(std::type_index(typeid(uint16_t)) == type)
2✔
296
  {
297
    return ValidCast<T, uint16_t>(val);
×
298
  }
299
  if(std::type_index(typeid(uint32_t)) == type)
2✔
300
  {
301
    return ValidCast<T, uint32_t>(val);
×
302
  }
303
  if(std::type_index(typeid(uint64_t)) == type)
2✔
304
  {
305
    return ValidCast<T, uint64_t>(val);
×
306
  }
307
  //------------
308
  if(std::type_index(typeid(int8_t)) == type)
2✔
309
  {
310
    return ValidCast<T, int8_t>(val);
×
311
  }
312
  if(std::type_index(typeid(int16_t)) == type)
2✔
313
  {
314
    return ValidCast<T, int16_t>(val);
×
315
  }
316
  if(std::type_index(typeid(int32_t)) == type)
2✔
317
  {
318
    return ValidCast<T, int32_t>(val);
1✔
319
  }
320
  if(std::type_index(typeid(int64_t)) == type)
1✔
321
  {
322
    return ValidCast<T, int64_t>(val);
1✔
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

336
inline Any& Any::operator=(const Any& other)
338✔
337
{
338
  if(this != &other)
338✔
339
  {
340
    this->_any = other._any;
338✔
341
    this->_original_type = other._original_type;
338✔
342
  }
343
  return *this;
338✔
344
}
345

346
inline Any& Any::operator=(Any&& other) noexcept
19,527✔
347
{
348
  this->_any = std::move(other._any);
19,527✔
349
  this->_original_type = other._original_type;
19,527✔
350
  return *this;
19,527✔
351
}
352

353
inline bool Any::isNumber() const
560✔
354
{
355
  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t) ||
708✔
356
         _any.type() == typeid(double);
708✔
357
}
358

359
inline bool Any::isIntegral() const
360
{
361
  return _any.type() == typeid(int64_t) || _any.type() == typeid(uint64_t);
362
}
363

364
inline void Any::copyInto(Any& dst) const
192✔
365
{
366
  if(dst.empty())
192✔
367
  {
368
    dst = *this;
39✔
369
    return;
39✔
370
  }
371

372
  const auto& dst_type = dst.castedType();
153✔
373

374
  if((castedType() == dst_type) || (isString() && dst.isString()))
153✔
375
  {
376
    dst._any = _any;
129✔
377
  }
378
  else if(isNumber() && dst.isNumber())
24✔
379
  {
380
    if(dst_type == typeid(int64_t))
23✔
381
    {
382
      dst._any = cast<int64_t>();
23✔
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");
1✔
400
  }
401
}
402

403
template <typename DST>
404
inline nonstd::expected<DST, std::string> Any::convert(EnableString<DST>) const
379✔
405
{
406
  const auto& type = _any.type();
379✔
407

408
  if(type == typeid(SafeAny::SimpleString))
379✔
409
  {
410
    return linb::any_cast<SafeAny::SimpleString>(_any).toStdString();
366✔
411
  }
412
  else if(type == typeid(int64_t))
13✔
413
  {
414
    return std::to_string(linb::any_cast<int64_t>(_any));
10✔
415
  }
416
  else if(type == typeid(uint64_t))
3✔
417
  {
418
    return std::to_string(linb::any_cast<uint64_t>(_any));
×
419
  }
420
  else if(type == typeid(double))
3✔
421
  {
422
    return std::to_string(linb::any_cast<double>(_any));
3✔
423
  }
424

425
  return nonstd::make_unexpected(errorMsg<DST>());
×
426
}
427

428
template <typename T>
429
inline nonstd::expected<T, std::string> Any::stringToNumber() const
15✔
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);
15✔
435
#if __cpp_lib_to_chars >= 201611L
436
  T out;
437
  auto [ptr, err] = std::from_chars(str.data(), str.data() + str.size(), out);
15✔
438
  if(err == std::errc())
15✔
439
  {
440
    return out;
11✔
441
  }
442
  else
443
  {
444
    return nonstd::make_unexpected("Any failed string to number conversion");
4✔
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
}
15✔
471

472
template <typename DST>
473
inline 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

493
template <typename DST>
494
inline nonstd::expected<DST, std::string> Any::convert(EnableArithmetic<DST>) const
2,209✔
495
{
496
  using SafeAny::details::convertNumber;
497
  DST out;
498

499
  const auto& type = _any.type();
2,209✔
500

501
  if(type == typeid(int64_t))
2,209✔
502
  {
503
    convertNumber<int64_t, DST>(linb::any_cast<int64_t>(_any), out);
1,666✔
504
  }
505
  else if(type == typeid(uint64_t))
543✔
506
  {
507
    convertNumber<uint64_t, DST>(linb::any_cast<uint64_t>(_any), out);
×
508
  }
509
  else if(type == typeid(double))
543✔
510
  {
511
    convertNumber<double, DST>(linb::any_cast<double>(_any), out);
538✔
512
  }
513
  else
514
  {
515
    return nonstd::make_unexpected(errorMsg<DST>());
5✔
516
  }
517
  return out;
2,197✔
518
}
519

520
template <typename T>
521
inline nonstd::expected<T, std::string> Any::tryCast() const
445,513✔
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())
445,513✔
527
  {
528
    throw std::runtime_error("Any::cast failed because it is empty");
×
529
  }
530

531
  if(castedType() == typeid(T))
445,513✔
532
  {
533
    return linb::any_cast<T>(_any);
442,891✔
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())
9✔
541
    {
542
      return static_cast<T>(convert<int>().value());
9✔
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())
2,613✔
555
  {
556
    if constexpr(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
557
    {
558
      if(auto out = stringToNumber<T>())
30✔
559
      {
560
        return out.value();
11✔
561
      }
562
      else
563
      {
564
        return out;
4✔
565
      }
566
    }
567
  }
568

569
  if(auto res = convert<T>())
5,189✔
570
  {
571
    return res.value();
2,567✔
572
  }
573
  else
574
  {
575
    return res;
24✔
576
  }
577
}
578

579
}  // end namespace BT
580

581
// Include registry after Any is defined, for tryCastWithRegistry implementation
582
#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp"
583

584
namespace BT
585
{
586

587
template <typename T>
588
inline nonstd::expected<T, std::string>
589
Any::tryCastWithRegistry(const PolymorphicCastRegistry& registry) const
15✔
590
{
591
  static_assert(is_shared_ptr<T>::value, "tryCastWithRegistry only works with shared_ptr "
592
                                         "types");
593

594
  if(_any.empty())
15✔
595
  {
NEW
596
    return nonstd::make_unexpected("Any::tryCastWithRegistry failed: empty value");
×
597
  }
598

599
  // Try to cast using the registry
600
  auto result = registry.tryCast(_any, _original_type, typeid(T));
15✔
601
  if(!result.empty())
15✔
602
  {
603
    try
604
    {
605
      return linb::any_cast<T>(result);
9✔
606
    }
NEW
607
    catch(const std::exception& e)
×
608
    {
NEW
609
      return nonstd::make_unexpected(StrCat("Polymorphic cast failed: ", e.what()));
×
610
    }
611
  }
612

613
  return nonstd::make_unexpected(StrCat("[Any::tryCastWithRegistry]: no polymorphic "
12✔
614
                                        "conversion from [",
615
                                        demangle(_original_type), "] to [",
6✔
616
                                        demangle(typeid(T)), "]"));
6✔
617
}
15✔
618

619
}  // end namespace BT
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc