I have a class with a predicate, for example, Object::isValid()
. I need to iterate over only valid objects, so I'm using ranges/views:
struct Object {
bool isValid() const;
// ...
};
// Usage:
std::vector<Object> objects = getObjects();
for (const auto &obj : objects |
std::ranges::view::filter(
[](const Object &obj) {
return obj.isValid();
})
) {
// Use obj that is of type `const Object &`
}
So far so good. Now I need to separate producer and consumer of valid objects view:
auto getValidObjectsView() {
return objects |
std::ranges::views::filter(
[](const Object &obj) {
return obj.isValid();
}
);
}
void consumeValidObjects(auto &&validObjectsView) {
for (const auto &obj : validObjectsView) {
// Use `obj`
}
}
consumeValidObjects(getValidObjectsView());
That also works, but now I need to hide the implementations of the functions into separate translation units exposing signatures in headers. Thus I cannot use auto
anymore.
The problem is that the return type of getValidObjectsView()
function depends on std::ranges::views::filter
and on the lambda used inside of this function, so I cannot simply use this type in function signatures:
using ObjectsIterable = // whatever compiler reports as decltype<getValidObjectsView()> from the previous snippet
ObjectsIterable getValidObjectsView();
void consumeValidObjects(const ObjectsIterable &)
consumeValidObjects(getValidObjectsView());
The question I have is how to define a proxy type ObjectsIterable
that erases the actual underlying type of what the producer returns, something like that:
using ObjectsIterable = std::ranges::view::one_size_fits_all_view<Object>;
Instead of type erasing the returned view, you can type erase the lambda by converting it to a function pointer, which allows the function's return type to be declared as
std::ranges::filter_view<
std::ranges::ref_view<std::vector<Object>>,
bool (*)(const Object&)
>
getValidObjectsView() {
return objects |
std::ranges::views::filter(
+[](const Object &obj) {
return obj.isValid();
}
);
}