C++ type-erasing generic functor objects

Viewed 102

I have a bunch of those classes that implement a generic call operator:

template<typename T = char>
struct match {

    template<initer_of_type<T> IterT>
    constexpr auto operator()(IterT iter) const -> std::optional<decltype(iter)> {
        // ... function body
    }
    // ... some members
}
// ... more similar structs

in a class template, where the operator itself is also a template.
NOTE: I'm using here a concept that I made to accept any input iterator that returns the specific value type:

template<typename IterT, typename T>
concept initer_of_type =
    std::input_iterator<IterT>
    && std::is_same_v<typename IterT::value_type, T>;

That way I can use the algorithm on any iterable Ts container...

I'd like to be able to hold an array of these objects, all of which have the same template parameter T but may be different classes. It seems like I cannot use neither plain inheritence nor type erasue (at least the way I know it) because the function is a template. Is there a good way to do this? Or am I looking for the wrong solution?

1 Answers

@chris comment is great, if my understanding is correct, basically it add an extra layer of abstraction on your IterT thus make the match function able to be erased(and then be stored in some container)

As you said:

I'd like to be able to hold an array of these objects, all of which have the same template parameter T but may be different classes.

So it seems that we must add a new abstraction layer to work with this constraint, chris's perspective is to start with the IterT. And i'd like to provide a new perspective stolen from ThreadExecutor.

Generally almost all ThreadExecutor have such interface:

std::future<R> execute(Func&& f, Args&&... args);

Just like your situation, the args and return type are both template parameter, and ThreadExecutor also need to store these user pass task in a container. Basically, ThreadExecutor handle this by wrapping the user pass task inside a packaged_task to erase the arg type, then wrap the packaged_task inside a function wrapper or something to erase the return type.

This is a very naive code snippet, which directly use value capture and so on, but i think it can express the basic idea.

struct Container{
    template<typename IterT>
    void add(match<IterT> func, IterT iter){
        store_.push_back(std::function([=](){
            std::packaged_task([=](){
                return func(iter);
            })();
        }));
    }

    std::vector<std::function<void()>> store_;
};
Related