Exagon
Exagon

Reputation: 5088

Interface with template member function

I am interested in implementing a Java-collection-like environment for C++. I know this isn't a good idea and so on but I don't really want to use it later, but just learn how to do some advanced OOP.

My problem is I want a base class template collection<T> with purely virtual functions. One of these functions should be map() which takes a std::function<R(T)>. Since map() should be virtual I don't know which return type I should use for it. collection<R> isn't possible because member function templates can't be virtual.

How can I add such map() member function for my collection<T> interface?

Upvotes: 9

Views: 1339

Answers (2)

Barry
Barry

Reputation: 302827

How can I add such map member function for my collection<T> interface?

The short answer is: you don't. If I have some collection<int> and I want to map std::to_string onto it, I need to produce a collection<std::string>. But a vector_collection<int> needs to produce a vector_collection<std::string> and a list_collection<int> needs to produce a list_collection<std::string> - so that type transformation itself needs to be virtualized. But you can't have virtual member function templates, so there's no way to express this.

In order for this to work, you would have to have a common base type for all of the objects you're putting in your container and then just have a common facade that you could cast between. That is, you really only have collection<unique_ptr<Object>> where map just gives you a different collection<unique_ptr<Object>>, and you just map your collection_facade<int, collection<unique_ptr<Object>>> into a collection_facade<std::string, collection<unique_ptr<Object>>>. With a lot of work and complete disregard for performance and type safety, you could get there.


This is the advantage of templates. If I want to write map for something like vector, I can just write that:

template <class T, class A, class F, class R = std::result_of_t<F(T)>>
std::vector<R, A> map(std::vector<T, A> const& v, F f) {
    std::vector<R, A> mapped;
    mapped.reserve(v.size());
    for (T const& elem : v) {
        mapped.push_back(f(elem));
    }
    return mapped;
}

or:

template <class T, class A, class F, class R = std::result_of_t<F(T)>>
std::vector<R, A> map(std::vector<T, A> const& v, F f) {
    return std::vector<R, A>(
        boost::make_transform_iterator(v.begin(), f),
        boost::make_transform_iterator(v.end(), f)
        );
}

I have to implement map() for each container separately - but I would have to do that anyway. And now I'm not giving anything up. Besides, how often are you writing algorithms that are runtime-container-agnostic?

Upvotes: 7

bipll
bipll

Reputation: 11940

Implement map as an external template function. For instance, you can decompose map in two stages, internal virtual producer and external templated consumer.

template<typename T> struct Collection {
    // virtual T next(); // Java way
    // C++ way
    // In simplest cases you can rely on iterator pairs.
    struct const_iterator {
        T const &operator*() const;
        const_iterator &operator++();
    }
    virtual const_iterator begin() const;
    virtual const_iterator end() const;
};
template<typename R, typename T> Collection<R> map(
    Collection<T> const &coll, std::function<R(T)> const &f);

To implement essentially complicated containers and monadic compositions you can even deny begin() and end() and write an explicit (partial) template specialization.

Upvotes: 0

Related Questions