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

BehaviorTree / BehaviorTree.CPP / 21671715805

04 Feb 2026 12:37PM UTC coverage: 80.192% (+0.4%) from 79.835%
21671715805

Pull #1107

github

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

193 of 213 new or added lines in 7 files covered. (90.61%)

2 existing lines in 1 file now uncovered.

4927 of 6144 relevant lines covered (80.19%)

20883.73 hits per line

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

91.33
/include/behaviortree_cpp/utils/polymorphic_cast_registry.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
#include "behaviortree_cpp/contrib/any.hpp"
16

17
#include <functional>
18
#include <map>
19
#include <memory>
20
#include <mutex>
21
#include <set>
22
#include <typeindex>
23

24
namespace BT
25
{
26

27
/**
28
 * @brief Registry for polymorphic shared_ptr cast relationships.
29
 *
30
 * This enables passing shared_ptr<Derived> to ports expecting shared_ptr<Base>
31
 * without breaking ABI compatibility. Users register inheritance relationships
32
 * at runtime, and the registry handles upcasting/downcasting transparently.
33
 *
34
 * This class is typically owned by BehaviorTreeFactory and passed to Blackboard
35
 * during tree creation. This avoids global state and makes testing easier.
36
 *
37
 * Usage with BehaviorTreeFactory:
38
 *   BehaviorTreeFactory factory;
39
 *   factory.registerPolymorphicCast<Cat, Animal>();
40
 *   factory.registerPolymorphicCast<Sphynx, Cat>();
41
 *   auto tree = factory.createTreeFromText(xml);
42
 */
43
class PolymorphicCastRegistry
44
{
45
public:
46
  using CastFunction = std::function<linb::any(const linb::any&)>;
47

48
  PolymorphicCastRegistry() = default;
299✔
49
  ~PolymorphicCastRegistry() = default;
299✔
50

51
  // Non-copyable, non-movable (contains mutex)
52
  PolymorphicCastRegistry(const PolymorphicCastRegistry&) = delete;
53
  PolymorphicCastRegistry& operator=(const PolymorphicCastRegistry&) = delete;
54
  PolymorphicCastRegistry(PolymorphicCastRegistry&&) = delete;
55
  PolymorphicCastRegistry& operator=(PolymorphicCastRegistry&&) = delete;
56

57
  /**
58
   * @brief Register a Derived -> Base inheritance relationship.
59
   *
60
   * This enables:
61
   * - Upcasting: shared_ptr<Derived> can be retrieved as shared_ptr<Base>
62
   * - Downcasting: shared_ptr<Base> can be retrieved as shared_ptr<Derived>
63
   *   (via dynamic_pointer_cast, may return nullptr if types don't match)
64
   *
65
   * @tparam Derived The derived class (must inherit from Base)
66
   * @tparam Base The base class (must be polymorphic - have virtual functions)
67
   */
68
  template <typename Derived, typename Base>
69
  void registerCast()
15✔
70
  {
71
    static_assert(std::is_base_of_v<Base, Derived>, "Derived must inherit from Base");
72
    static_assert(std::is_polymorphic_v<Base>, "Base must be polymorphic (have virtual "
73
                                               "functions)");
74

75
    std::lock_guard<std::mutex> lock(mutex_);
15✔
76

77
    // Register upcast: Derived -> Base
78
    auto upcast_key = std::make_pair(std::type_index(typeid(std::shared_ptr<Derived>)),
15✔
79
                                     std::type_index(typeid(std::shared_ptr<Base>)));
15✔
80

81
    upcasts_[upcast_key] = [](const linb::any& from) -> linb::any {
26✔
82
      auto ptr = linb::any_cast<std::shared_ptr<Derived>>(from);
11✔
83
      return std::static_pointer_cast<Base>(ptr);
22✔
84
    };
11✔
85

86
    // Register downcast: Base -> Derived (uses dynamic_pointer_cast)
87
    auto downcast_key = std::make_pair(std::type_index(typeid(std::shared_ptr<Base>)),
15✔
88
                                       std::type_index(typeid(std::shared_ptr<Derived>)));
15✔
89

90
    downcasts_[downcast_key] = [](const linb::any& from) -> linb::any {
20✔
91
      auto ptr = linb::any_cast<std::shared_ptr<Base>>(from);
5✔
92
      auto derived = std::dynamic_pointer_cast<Derived>(ptr);
5✔
93
      if(!derived)
5✔
94
      {
95
        throw std::bad_cast();
4✔
96
      }
97
      return derived;
2✔
98
    };
9✔
99

100
    // Track inheritance relationship for port compatibility checks
101
    base_types_[std::type_index(typeid(std::shared_ptr<Derived>))].insert(
30✔
102
        std::type_index(typeid(std::shared_ptr<Base>)));
15✔
103

104
    // Also track direct type relationships (without shared_ptr wrapper)
105
    direct_base_types_[std::type_index(typeid(Derived))].insert(
30✔
106
        std::type_index(typeid(Base)));
15✔
107
  }
15✔
108

109
  /**
110
   * @brief Check if from_type can be converted to to_type.
111
   *
112
   * Returns true if:
113
   * - from_type == to_type
114
   * - from_type is a registered derived type of to_type (upcast)
115
   * - to_type is a registered derived type of from_type (downcast)
116
   */
117
  [[nodiscard]] bool isConvertible(std::type_index from_type,
118
                                   std::type_index to_type) const
119
  {
120
    if(from_type == to_type)
121
    {
122
      return true;
123
    }
124

125
    std::lock_guard<std::mutex> lock(mutex_);
126

127
    // Check direct upcast
128
    auto upcast_key = std::make_pair(from_type, to_type);
129
    if(upcasts_.find(upcast_key) != upcasts_.end())
130
    {
131
      return true;
132
    }
133

134
    // Check transitive upcast (e.g., Sphynx -> Cat -> Animal)
135
    if(canUpcastTransitive(from_type, to_type))
136
    {
137
      return true;
138
    }
139

140
    // Check downcast
141
    auto downcast_key = std::make_pair(from_type, to_type);
142
    if(downcasts_.find(downcast_key) != downcasts_.end())
143
    {
144
      return true;
145
    }
146

147
    return false;
148
  }
149

150
  /**
151
   * @brief Check if from_type can be UPCAST to to_type (not downcast).
152
   *
153
   * This is stricter than isConvertible - only allows going from
154
   * derived to base, not the reverse.
155
   */
156
  [[nodiscard]] bool canUpcast(std::type_index from_type, std::type_index to_type) const
8✔
157
  {
158
    if(from_type == to_type)
8✔
159
    {
NEW
160
      return true;
×
161
    }
162

163
    std::lock_guard<std::mutex> lock(mutex_);
8✔
164
    return canUpcastTransitive(from_type, to_type);
8✔
165
  }
8✔
166

167
  /**
168
   * @brief Attempt to cast the value to the target type.
169
   *
170
   * @param from The source any containing a shared_ptr
171
   * @param from_type The type_index of the stored type
172
   * @param to_type The target type_index
173
   * @return The casted any, or empty any if cast fails
174
   */
175
  [[nodiscard]] linb::any tryCast(const linb::any& from, std::type_index from_type,
15✔
176
                                  std::type_index to_type) const
177
  {
178
    if(from_type == to_type)
15✔
179
    {
NEW
180
      return from;
×
181
    }
182

183
    std::lock_guard<std::mutex> lock(mutex_);
15✔
184

185
    // Try direct upcast
186
    auto upcast_key = std::make_pair(from_type, to_type);
15✔
187
    auto upcast_it = upcasts_.find(upcast_key);
15✔
188
    if(upcast_it != upcasts_.end())
15✔
189
    {
190
      try
191
      {
192
        return upcast_it->second(from);
5✔
193
      }
NEW
194
      catch(...)
×
195
      {
NEW
196
        return linb::any{};
×
NEW
197
      }
×
198
    }
199

200
    // Try transitive upcast
201
    auto transitive_result = tryTransitiveUpcast(from, from_type, to_type);
10✔
202
    if(!transitive_result.empty())
10✔
203
    {
204
      return transitive_result;
3✔
205
    }
206

207
    // Try direct downcast
208
    auto downcast_key = std::make_pair(from_type, to_type);
7✔
209
    auto downcast_it = downcasts_.find(downcast_key);
7✔
210
    if(downcast_it != downcasts_.end())
7✔
211
    {
212
      try
213
      {
214
        return downcast_it->second(from);
4✔
215
      }
216
      catch(...)
3✔
217
      {
218
        return linb::any{};
3✔
219
      }
3✔
220
    }
221

222
    // Try transitive downcast
223
    auto transitive_down_result = tryTransitiveDowncast(from, from_type, to_type);
3✔
224
    if(!transitive_down_result.empty())
3✔
225
    {
NEW
226
      return transitive_down_result;
×
227
    }
228

229
    return linb::any{};
3✔
230
  }
15✔
231

232
  /**
233
   * @brief Get all registered base types for a given type.
234
   */
235
  [[nodiscard]] std::set<std::type_index> getBaseTypes(std::type_index type) const
236
  {
237
    std::lock_guard<std::mutex> lock(mutex_);
238
    auto it = base_types_.find(type);
239
    if(it != base_types_.end())
240
    {
241
      return it->second;
242
    }
243
    return {};
244
  }
245

246
  /**
247
   * @brief Clear all registrations (mainly for testing).
248
   */
249
  void clear()
250
  {
251
    std::lock_guard<std::mutex> lock(mutex_);
252
    upcasts_.clear();
253
    downcasts_.clear();
254
    base_types_.clear();
255
    direct_base_types_.clear();
256
  }
257

258
private:
259
  // Check if we can upcast from_type to to_type through a chain of registered casts
260
  [[nodiscard]] bool canUpcastTransitive(std::type_index from_type,
8✔
261
                                         std::type_index to_type) const
262
  {
263
    // BFS to find a path from from_type to to_type
264
    std::set<std::type_index> visited;
8✔
265
    std::vector<std::type_index> queue;
8✔
266
    queue.push_back(from_type);
8✔
267

268
    while(!queue.empty())
15✔
269
    {
270
      auto current = queue.back();
9✔
271
      queue.pop_back();
9✔
272

273
      if(visited.count(current) != 0)
9✔
274
      {
275
        continue;
6✔
276
      }
277
      visited.insert(current);
9✔
278

279
      auto it = base_types_.find(current);
9✔
280
      if(it == base_types_.end())
9✔
281
      {
282
        continue;
6✔
283
      }
284

285
      for(const auto& base : it->second)
4✔
286
      {
287
        if(base == to_type)
3✔
288
        {
289
          return true;
2✔
290
        }
291
        queue.push_back(base);
1✔
292
      }
293
    }
294
    return false;
6✔
295
  }
8✔
296

297
  // Try to perform a transitive upcast
298
  [[nodiscard]] linb::any tryTransitiveUpcast(const linb::any& from,
10✔
299
                                              std::type_index from_type,
300
                                              std::type_index to_type) const
301
  {
302
    // Find path from from_type to to_type using BFS
303
    // Note: std::type_index has no default constructor, so we can't use operator[]
304
    std::map<std::type_index, std::type_index> parent;
10✔
305
    std::vector<std::type_index> queue;
10✔
306
    queue.push_back(from_type);
10✔
307
    parent.insert({ from_type, from_type });
10✔
308

309
    while(!queue.empty())
25✔
310
    {
311
      auto current = queue.back();
18✔
312
      queue.pop_back();
18✔
313

314
      auto it = base_types_.find(current);
18✔
315
      if(it == base_types_.end())
18✔
316
      {
317
        continue;
7✔
318
      }
319

320
      for(const auto& base : it->second)
19✔
321
      {
322
        if(parent.find(base) != parent.end())
11✔
323
        {
NEW
324
          continue;
×
325
        }
326
        parent.insert({ base, current });
11✔
327
        if(base == to_type)
11✔
328
        {
329
          // Found path, reconstruct and apply casts
330
          std::vector<std::type_index> path;
3✔
331
          auto node = to_type;
3✔
332
          while(node != from_type)
9✔
333
          {
334
            path.push_back(node);
6✔
335
            node = parent.at(node);
6✔
336
          }
337
          path.push_back(from_type);
3✔
338
          std::reverse(path.begin(), path.end());
3✔
339

340
          // Apply casts along the path
341
          linb::any current_value = from;
3✔
342
          for(size_t i = 0; i + 1 < path.size(); ++i)
9✔
343
          {
344
            auto cast_key = std::make_pair(path[i], path[i + 1]);
6✔
345
            auto cast_it = upcasts_.find(cast_key);
6✔
346
            if(cast_it == upcasts_.end())
6✔
347
            {
NEW
348
              return linb::any{};
×
349
            }
350
            try
351
            {
352
              current_value = cast_it->second(current_value);
6✔
353
            }
NEW
354
            catch(...)
×
355
            {
NEW
356
              return linb::any{};
×
NEW
357
            }
×
358
          }
359
          return current_value;
3✔
360
        }
3✔
361
        queue.push_back(base);
8✔
362
      }
363
    }
364
    return linb::any{};
7✔
365
  }
10✔
366

367
  // Try to perform a transitive downcast
368
  [[nodiscard]] linb::any tryTransitiveDowncast(const linb::any& from,
3✔
369
                                                std::type_index from_type,
370
                                                std::type_index to_type) const
371
  {
372
    // For downcast, we need to find if to_type has from_type as an ancestor
373
    // Then apply downcasts in reverse order
374
    // Note: std::type_index has no default constructor, so we can't use operator[]
375
    std::map<std::type_index, std::type_index> parent;
3✔
376
    std::vector<std::type_index> queue;
3✔
377
    queue.push_back(to_type);
3✔
378
    parent.insert({ to_type, to_type });
3✔
379

380
    while(!queue.empty())
8✔
381
    {
382
      auto current = queue.back();
6✔
383
      queue.pop_back();
6✔
384

385
      auto it = base_types_.find(current);
6✔
386
      if(it == base_types_.end())
6✔
387
      {
388
        continue;
2✔
389
      }
390

391
      for(const auto& base : it->second)
7✔
392
      {
393
        if(parent.find(base) != parent.end())
4✔
394
        {
NEW
395
          continue;
×
396
        }
397
        parent.insert({ base, current });
4✔
398
        if(base == from_type)
4✔
399
        {
400
          // Found path, reconstruct and apply downcasts
401
          std::vector<std::type_index> path;
1✔
402
          auto node = from_type;
1✔
403
          while(node != to_type)
3✔
404
          {
405
            path.push_back(node);
2✔
406
            node = parent.at(node);
2✔
407
          }
408
          path.push_back(to_type);
1✔
409

410
          // Apply downcasts along the path
411
          linb::any current_value = from;
1✔
412
          for(size_t i = 0; i + 1 < path.size(); ++i)
1✔
413
          {
414
            auto cast_key = std::make_pair(path[i], path[i + 1]);
1✔
415
            auto cast_it = downcasts_.find(cast_key);
1✔
416
            if(cast_it == downcasts_.end())
1✔
417
            {
418
              return linb::any{};
1✔
419
            }
420
            try
421
            {
422
              current_value = cast_it->second(current_value);
1✔
423
            }
424
            catch(...)
1✔
425
            {
426
              return linb::any{};
1✔
427
            }
1✔
428
          }
NEW
429
          return current_value;
×
430
        }
1✔
431
        queue.push_back(base);
3✔
432
      }
433
    }
434
    return linb::any{};
2✔
435
  }
3✔
436

437
  mutable std::mutex mutex_;
438
  std::map<std::pair<std::type_index, std::type_index>, CastFunction> upcasts_;
439
  std::map<std::pair<std::type_index, std::type_index>, CastFunction> downcasts_;
440
  std::map<std::type_index, std::set<std::type_index>> base_types_;
441
  std::map<std::type_index, std::set<std::type_index>> direct_base_types_;
442
};
443

444
}  // 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