I would like to know if its possible to concatenate two vectors holding different type of objects, such that I can then iterate on the concatenation and call a common interface.
Something like this:
std::vector<A> as;
as.resize(3);
std::vector<B> bs;
bs.resize(4);
for (const auto &v : ranges::views::concat(as, bs))
{
foo(v);
}
You can find a full example here https://godbolt.org/z/nr5hhWMxj
CodePudding user response:
You can use views::transform
to convert the elements of both containers to std::variant
for type erasure.
auto to_variant = ranges::views::transform(
[](auto x) { return std::variant<A, B>(std::move(x)); });
for (const auto& v : ranges::views::concat(as | to_variant, bs | to_variant)) {
std::visit([](const auto& v) { foo(v); }, v);
}
CodePudding user response:
The problem with that is simple:
What is the common interface?
If they have a common type, like one is descended from the other, views::concat()
is intelligent enough to go there, and you still have dynamic dispatch for virtual
member functions.
If they are both descended from the same base (but neither is the base), you would be able to get the above by using views::transform()
.
Otherwise, you would need to views::transform()
them into a proxy-object which can reference either, and dispatches the members you care about appropriately.
While std::variant
cannot be it, unless you are fine with a copy, std::variant
storing std::reference_wrapper
s may work.
auto map = ranges::views::transform([](auto& x) {
return std::variant<std::reference_wrapper<A>, std::reference_wrapper<B>>(std::ref(x)); });
for (const auto& v : ranges::views::concat(as | map, bs | map))
std::visit([](auto x) {
auto& v = x.get();
foo(v);
}, v);
Alternative
Use static_for_each
:
template <class F, class T>
constexpr void static_for_each(F&& f, T&& t) {
std::apply([&](auto&&... x){
((f(std::forward<decltype(x)>(x)), void()), ...);
}, t);
}
static_for_each([&](auto&& c){
for (auto&& v : c)
foo(v);
}, std::forward_as_tuple(as, bs));