Oleg Andriyanov
Oleg Andriyanov

Reputation: 5269

Non-template function that processes any container of elements of specific type

I'd like to have a function as described in title.

I've noticed that STL algorithms that work with containers of any type (list, vector, etc) containing elements of any type (int, double) provide genericity by using iterator types as template parameters, e.g.

template<typename _II, typename _OI>
inline _OI
copy(_II __first, _II __last, _OI __result)

This is a good method until the algorithm works for any type of elements. The only requirement for element type is that it must have copy constructor.

But suppose we have one concrete type

class MyElement
{
    public:
    void doSomethingWithElement();
};

and we want to implement a function that processes number of elements of this type by calling function doSomethingWithElement().

Writing a function that receives container of specific type is not very convenient because many containers are treated in the same way (e.g. iterators), and if there will be need for processing containers of different types we'll be forced to duplicate the code. Writing a template works fine, but it seems ugly because we have to implement function in place where it is declared (in header file). Also, when we want to process elements of only one type, parametrizing this type is not the right way to achieve the goal.

I've been thinking about iterator interface that could be used like

void processContainer(IIterator<MyElement> begin, IIterator<MyElement> end);

If this iterator had pure virtual operator++ and operator* that were implemented in derived classes, we could pass such objects to processContainer. But there is a problem: if IIterator is abstract class, we can't instantiate it in the implementation of processContainer, and if we pass a pointer to IIterator, this function will be able to modify it.

Does anybody know any other hack to do this? Or would be another approach better than these ones above? Thanks in advance.

Upvotes: 4

Views: 810

Answers (4)

willj
willj

Reputation: 2999

It's not possible to create an abstract iterator that has the full iterator functionality - including the ability to make copies of itself - without changing the iterator interface. However, you can implement a subset of iterator functionality using an abstract base class:

#include <iterator>
#include <vector>
#include <list>


template<typename T>
struct AbstractIterator
{
    virtual bool operator!=(const AbstractIterator<T>& other) const = 0;
    virtual void operator++() = 0;
    virtual T& operator*() = 0;
};

template<typename Iterator>
struct ConcreteIterator : AbstractIterator<typename std::iterator_traits<Iterator>::value_type>
{
    typedef typename std::iterator_traits<Iterator>::value_type value_type;
    Iterator i;
    ConcreteIterator(Iterator i) : i(i)
    {
    }
    virtual bool operator!=(const AbstractIterator<value_type>& other) const
    {
        return i != static_cast<const ConcreteIterator*>(&other)->i;
    }
    virtual void operator++()
    {
        ++i;
    }
    virtual value_type& operator*()
    {
        return *i;
    }
};

template<typename Iterator>
ConcreteIterator<Iterator> wrapIterator(Iterator i)
{
    return ConcreteIterator<Iterator>(i);
}


class MyElement
{
public:
    void doSomethingWithElement();
};

void processContainerImpl(AbstractIterator<MyElement>& first, AbstractIterator<MyElement>& last)
{
    for(; first != last; ++first)
    {
        (*first).doSomethingWithElement();
    }
}

template<typename Iterator>
void processContainer(Iterator first, Iterator last)
{
    ConcreteIterator<Iterator> wrapFirst = wrapIterator(first);
    ConcreteIterator<Iterator> wrapLast = wrapIterator(last);
    return processContainerImpl(wrapFirst, wrapLast);
}

int main()
{
    std::vector<MyElement> v;
    processContainer(v.begin(), v.end());

    std::list<MyElement> l;
    processContainer(l.begin(), l.end());
}

Upvotes: 0

Edward Strange
Edward Strange

Reputation: 40859

You can't do exactly what you want to do. You could use enable_if to limit the function's availability:

template < typename Container >
typename enable_if<is_same<typename Container::value_type, MyElement>,void>::type processContainer(Container c)...

Upvotes: 0

Marius
Marius

Reputation: 2273

You could use std::for_each(): http://www.cplusplus.com/reference/algorithm/for_each/

full code:

void callDoSomething(MyElement &elem)
{
    elem.doSomething();
}

int main()
{
  std::vector<MyElement> vec(100);
  std::for_each(vec.begin(), vec.end(), callDoSomething);
}

Upvotes: 0

The simpler approach is to ignore the restriction and just implement your function as a template for any iterator. If the iterator does not refer to the type, then the user will get a horrible error message in the lines of "type X does not have doSomethingWithElement member function`.

The next thing would be to provide a static_assert, the function would still take any iterator (meaning that it will participate in overload resolution for any type) but the error message will be slightly more informative.

Furthermore you can decide to use SFINAE to remove the overload from the set of candidates. But while SFINAE is a beautiful golden hammer, I am not sure that you have the appropriate nail at hand.

If you really want to go further down the lane, you can take a look at any_iterator in the Adobe libraries as an example on how to perform type erasure on the iterator type to avoid the template. The complexity of this approach is orders of magnitude higher than any of the previous, and the runtime cost will also be higher, providing the only advantage of a cleaner ABI and less code size (different iterators can be passed to a single function).

Upvotes: 5

Related Questions