Search code examples
c++c++20type-erasurestd-ranges

How to define a type-erased ranges::view?


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>;

Solution

  • 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();
                   }
               );
    }