5

I'm currently designing an interface (Base in the following example) which offers a couple of virtual methods including begin() and end(). These two methods simple return the corresponding iterator like in any other collection like class. Derived classes should implement these methods and return their specific implementation of an iterator.

The following (simplified) example shows a derived class which uses a boost::transform_iterator to convert the private internal list of integers. This implementation is only an example in reality the iterated "thing" can be something else and so does the iterator.

The example works but i have one problem. The object type in main() doesn't hide the fact that the used iterator is of type TransformIterator. The base class will be used in some kind of plugin architecture where every plugin is a shared library. The plugins shouldn't know which type of iterator is used and should solely depend on the abstract interface. Is there a way to do this?

#include <boost/iterator/transform_iterator.hpp>

#include <iostream>
#include <string>
#include <vector>

template<class Iterator>
class Base
{
public:
    virtual Iterator begin() = 0;
    virtual Iterator end() = 0;
};

struct toString
{
    std::string operator()(int number) const
    {
        return std::to_string(number);
    }
};

typedef boost::transform_iterator<toString, std::vector<int>::iterator> TransformIterator;

class Derived : public Base<TransformIterator>
{
public:
    Derived() : ints{132, 3, 6451, 12, 5} {}

    TransformIterator begin()
    {
        return boost::make_transform_iterator(ints.begin(), toString());
    }

    TransformIterator end()
    {
        return boost::make_transform_iterator(ints.end(), toString());
    }

private:
    std::vector<int> ints;
};

int main()
{
    Base<TransformIterator>* obj = new Derived();
    for(const auto& value : *obj)
    {
        std::cout << value.size() << std::endl;
    }
    return 0;
}

A little bit more background: This specific example is based on an interface which reads configuration files. Currently i only plan to give a implementation for YAML files but other formats like XML or old school INI are also possible. Thus a common interface.

rmi
  • 532
  • 4
  • 15
tea2code
  • 1,007
  • 1
  • 8
  • 15

2 Answers2

6

I recently did something very similar in one of my projects at work. The way I did it was introduce an abstract iterator interface and a class for the iterator accessing that interface. Here's a simplified version:

template <class ValueType>
struct AbstractIterator
{
  virtual ValueType& dereference() const = 0;
  virtual void increment() = 0;
  // and so on...
};


template <class ValueType>
struct ClientIterator : public std::iterator<whatever>
{
  std::unique_ptr<AbstractIterator<ValueType>> it;

  ClientIterator(std::unique_ptr<AbstractIterator<ValueType>> it) : it(std::move(it)) {}

  ValueType& operator* const () { return it->dereference(); }
  ClientIterator& operator++ () { it>increment(); return *this; }

  // and so on...
};

struct Base
{
  typedef ClientIterator<std::string> Iterator;
  virtual Iterator begin() = 0;
  virtual Iterator end() = 0;
}


struct Derived : Base
{
  struct ServerIterator : AbstractIterator<std::string>
  {
    boost::transform_iterator<toString, std::vector<int>::iterator> it;

    virtual std::string& dereference() const override { return *it; }
    virtual void increment() override { ++it; }
  }

  virtual Iterator begin() override { return Iterator({new ServerIterator(something)}); }
  virtual Iterator end() override { return Iterator({new ServerIterator(anotherThing)}); }
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Thank you i will check your example later. – tea2code Mar 12 '14 at 12:10
  • `virtual` + `override` seems a bit verbose, why not only `override`? – TemplateRex Mar 12 '14 at 14:45
  • @TemplateRex No reason, just force of habit of always putting `virtual` on overriders (from the time there was no `override`). – Angew is no longer proud of SO Mar 12 '14 at 14:50
  • I like this implementation because it still allows you to use the iterator by value as you normally do, i.e. `Derived d; for (auto it = d.begin(); it != d.end(); ++it) { foo(*it); }`. A simpler implementation could just return a pointer to an `AbstractIterator` without wrapping it in a `ClientIterator`, but then you would need to write something like `Derived d; for (AbstractIterator *it = d.begin(); *it != d.end(); ++*it) { foo(**it); }`. Not that much extra code, but easy to make mistakes... – PieterNuyts Dec 12 '19 at 10:54
  • Shouldn't the constructor be defined with move semantics? I.e. `ClientIterator(std::unique_ptr> &&it)` – PieterNuyts Dec 12 '19 at 10:58
  • A disadvantage is that your underlying iterator needs to derive from `AbstractIterator`, which isn't the case for e.g. `std::vector::iterator`. You could solve that by defining `struct IteratorWrapper : AbstractIterator`, and then using `IteratorWrapper::iterator> { IteratorType it; /* iterator methods go here */ };`, but then it starts to become tedious. – PieterNuyts Dec 12 '19 at 11:04
3

Not with classic C++ iterators, no. They aren't intended for polymorphic use.

What you can do is define an abstract base class for your iterators, which is then implemented by a (templated) wrapper around each concrete iterator type. The abstract base class simply defines all required operators as pure virtual. The downside is every operation on the iterator will need a virtual function call... Depending on your use this may or may not become an issue (usually not unless you use them to frequently iterate over very large collections).

Example:

template <typename T>
class InputIterator<T>
{
  public:
    virtual T operator*() const = 0;

    // Other operators go here
};

template <typename TIterator>
class ConcreteInputIterator final
:  public InputIterator<typename std::iterator_traits<TIterator>::value_type>
{
  public:
    ConcreteInputIterator(TIterator iterator) : m_iterator(iterator) {}

  public:
    virtual T operator*() const override
    {
      return *m_iterator;
    };

  private:
    TIterator m_iterator;
};
heinrichj
  • 562
  • 2
  • 5
  • Ah now i understand why didn't find a solution. In this specific case the virtual function calls shouldn't be of any problem. The configuration is read once at start up. – tea2code Mar 12 '14 at 12:09
  • 1
    Note: for use with STL algorithms you'll want to specialize std::iterator_traits for your abstract iterator(s), or derive them from std::iterator which already has the proper specializations. – heinrichj Mar 12 '14 at 12:12