We want to implement a tree of items:
+----------------------+
| Item (interface) |
+----------------------+
| + getName(): string |-----------+
+----------------------+ |
^ |
| |
+------+------+ |
| | |
+-------------+ +------------+ |
| SimpleItem | | Group |<>--+
+-------------+ +------------+
| +add(Item) |
| +all() |
+------------+
The Group::all() function shall return a flattened recursive view of all items including self, as pairs of: {recursion level, item}.
An implementation using ranges::views
fails, as the return type of all, depends on the level (a view of a view of items is not the same type as a view of items).
So this code doesn't compile:
auto all(size_t level = 0) const {
return views::concat(
views::single(std::pair<size_t, std::shared_ptr<const Item>>(level, shared_from_this())),
views::join(items |
views::transform([level](const auto& item) {
auto group = std::dynamic_pointer_cast<Group>(item);
if (group == nullptr) {
return views::single(std::pair{level + 1, item});
}
else {
return group->all(level + 1);
}
})
)
);
}
Code: https://compiler-explorer.com/z/fdc8rsWae
Any idea how to make the all function work and return the desired flattened view of all recursive items?
* Note: the code uses ranges-v3, as we use concat, which is added to std::ranges only in C++26. Solutions may rely either on std::ranges or on ranges-v3.
A function needs to have a single return type, and every time concat
is called recursively the type changes, you need to type-erase the result at some point, like the return of the lambda.
one way to type-erase ranges is ranges::any_view.
using erased_view = ranges::any_view<std::pair<size_t, std::shared_ptr<const Item>>,ranges::category::input>;
auto all(size_t level = 0) const {
return views::concat(
views::single(std::pair<size_t, std::shared_ptr<const Item>>(level, shared_from_this())),
views::join(items |
views::transform([level](const auto& item)->erased_view {
auto group = std::dynamic_pointer_cast<Group>(item);
if (group == nullptr) {
return views::single(std::pair{level + 1, item});
}
else {
return group->all(level + 1);
}
})
)
);
}