Iterator over pure virtual container interface in C++ - c++

I have a pure virtual interface to a container which is more or less like this:
class IContainer
{
public:
virtual ~IContainer() = default;
virtual Element& operator[](size_t index) = 0;
virtual const Element& operator[](size_t index) const = 0;
virtual size_t size() const = 0;
};
I would like to make use of range for loops, so I need to define begin() and end(). In order to do so, I need to define the iterator type as well.
It should be not be particularly hard, but nevertheless I would like to know if is there already anything in STL or Boost that can come to help, before I start coding something that already exists.

It might not be a good idea, but adding for(:) loop support is relatively easy here. I'll be minimal.
I'll create an iteroid, a not-iterator that is enough to support for(:) loops. This requires ++, != and unary * support, and nothing else.
template<class C>
struct index_iteroid {
decltype(auto) operator*()const {
return (*container)[i];
}
index_iteroid(index_iteroid const&)=default;
index_iteroid& operator=(index_iteroid const&)=default;
friend bool operator==(index_iteroid const& lhs, index_iteroid const& rhs) {
return std::tie(lhs.i, lhs.container)==std::tie(rhs.i, rhs.container);
}
friend bool operator!=(index_iteroid const& lhs, index_iteroid const& rhs) {
return !(lhs==rhs);
}
void operator++()&{
++i;
}
index_iteroid(C* c, std::size_t in):i(in), container(c) {}
private:
std::size_t i = 0;
C* container = nullptr;
};
now we use it:
class IContainer
{
public:
virtual ~IContainer() = default;
virtual Element& operator[](size_t index) = 0;
virtual const Element& operator[](size_t index) const = 0;
virtual size_t size() const = 0;
index_iteroid<IContainer> begin() { return {this, 0}; }
index_iteroid<IContainer> end() { return {this, size()}; }
index_iteroid<IContainer const> begin() const { return {this, 0}; }
index_iteroid<IContainer const> end() const { return {this, size()}; }
};
and there you have it.
void test( IContainer* cont ) {
if (!cont) return;
for(Element& e : *cont) {
// code
}
}
please excuse any typos.
Now a full iterator takes about 2-3 times as much code as my iteroid does, but nothing tricky, just annoying boilerplate mostly.
The standard doesn't have much to help you. For boost, you could compose a counting iterator with a function caling iterator/generator, and have the function call use []. Boost also has some utilities to make it take less boilerplate to write a full iterator, if you want to upgrade the iteroid to an iterator.

C++ doesn't do "interfaces" like this. The idiomatic way is for the (potential) clients of IContainer to instead be templated over the container type and just call values[index], or be templated over an iterator type and call like *(first + offset).
In C++20 you will be able to write a Container Concept, which behaves somewhat like an interface definition, but you can already express a Concept as documented requirements.
If instead you want a type-erased random access "container", you can use boost::any_range<Element, boost::random_access_traversal_tag>

Related

C++17 custom iterator in a pimpl idiom context

I want to know how to have a c++ class to be iterable (stl compatible) without exposing the implementation ?
The structure of the project is like :
Stream
class Stream
{
public:
Stream();
[...]
StreamIterator iter()
{
return StreamIterator(this);
}
private:
class impl;
std::unique_ptr<impl> pimpl;
};
StreamFilter
class StreamFilter
{
public:
StreamFilter();
[...]
private:
class impl;
std::shared_ptr<impl> pimpl;
};
StreamIterator
class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};
StreamFilter is a base class for differents filtering strategies.
For simplicity in the sample code, I used raw memory pointers, of course in a real exploitation code I use intelligents pointers : shared, weak ...
I want to allow the StreamIterator become iterable in a STL way, doing :
StreamIterator iter = stream.iter();
iter.addFilter(new FilterByOffset([...with parameters...]));
for (auto item : iter)
{
[...doing something with filtered items...]
}
The basic way is to add some accessors to allow range-based for loop.
StreamIterator
class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};
Where iterator and const_iterator are basically typedef's to the internal container iterators. And this is the problem.
First, I don't want to expose private implementation in the StreamIterator header. And so, iterator and const_iterator are not allowed here.
Second, because of the stream filtering strategy, the iterators returned are not just alias to some internal stl containers. In the internal implementation, I need to call the filters in a functor way to check if the item need to be exclude or not.
The only type allowed in the StreamIterator header is the type of the item object returned.
Is there a way to do that?
Thank you very much!
Additional Information:
Maybe this declaration is a way to allow a private implementation, I need to investigate more :
StreamIterator
class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
struct iterator
{
Object::Ptr operator*();
iterator& operator++();
bool operator!= (const iterator& it) const;
};
typedef typename StreamIterator::iterator iterator;
iterator begin();
iterator end();
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};
First, don't call it StreamIterator; an iterator is a pointer-like object for which 2 of them can specify a range. Your StreamIterator doesn't have this. Nothing good can come of reusing the well defined iterator term here.
Now, your StreamIterator is some kind of range of iterators. So we'll call it a StreamRange.
In order to hide how StreamRange can be iterated over, you have to hide how the iterators it uses work.
And this -- hiding the implementation detalis of a stream iterator -- has a substantial cost to it.
When iterating over a loop, each step in the loop involves ++ and * and an ==. Throwing a pointer indirection and a vtable lookup (or equivalent) on each of those will make your loops much slower.
But here is how to do it.
template<class Value>
struct any_input_iterator {
using difference_type = std::ptrdiff_t;
using value_type=Value;
using pointer = value_type*;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
private:
struct vtable_t {
bool(*equal)( std::any const& lhs, std::any const& rhs ) = 0;
Value(*get)( std::any& ) = 0;
void(*inc)( std::any& ) = 0;
};
vtable_t const* vtable = 0;
std::any state;
template<class U>
static vtable_t make_vtable() {
return {
[](std::any const& lhs, std::any const& rhs)->bool {
return std::any_cast<U const&>(lhs) == std::any_cast<U const&>(rhs);
},
[](std::any& src)->Value {
return *std::any_cast<U&>(src);
},
[](std::any& src) {
++std::any_cast<U&>(src);
}
};
}
template<class U>
static vtable_t const* get_vtable() {
static const auto vtable = make_vtable<U>();
return &vtable;
}
public:
template<class U,
std::enable_if_t<!std::is_same<std::decay_t<U>, any_input_iterator>{}, bool> = true
>
any_input_iterator(U&& u):
vtable(get_vtable<std::decay_t<U>>()),
state(std::forward<U>(u))
{}
any_input_iterator& operator++() { vtable->inc(state); return *this; }
any_input_iterator operator++(int) { auto tmp = *this; ++*this; return tmp; }
reference operator*() { return vtable->get(state); }
friend bool operator==( any_input_iterator const& lhs, any_input_iterator const& rhs ) {
if (lhs.vtable != rhs.vtable) return false;
if (!lhs.vtable) return true;
return lhs.vtable->equal( lhs.state, rhs.state );
}
friend bool operator!=( any_input_iterator const& lhs, any_input_iterator const& rhs ) {
return !(lhs==rhs);
}
struct fake_ptr {
Value t;
Value* operator->()&&{ return std::addressof(t); }
};
fake_ptr operator->()const { return {**this}; }
};
there are probably some typoes, but this is basic type erasure. Boost does a better job at this.
I only supported input iterators. If you want to support forward iterators, you have to change up some typedefs and return references and the like.
any_input_iterator<int> begin();
any_input_iterator<int> end();
that is, however, enough to let someone iterate over your range in question.
It will be slow, but it will work.

How to realize a custom implementation of a std-like iterator?

I have written a very simple file managing database that basicly looks like this:
class FileDB
{
public:
FileDB(std::string dir) : rootDir(dir) { }
void loadFile(std::string filename, File &file) const;
void saveFile(std::string filename, const File &file) const;
private:
std::string rootDir;
}
Now I would like to iterate through all files contained in the database like using a std::iterator:
void iterateFiles()
{
FileDB filedb("C:\\MyFiles");
for (FileDB::iterator file_it = filedb.begin(); file_it != filedb.end(); ++file_it)
{
File f = *file_it;
// do something with file
}
}
I've read answers to similar questions, some suggesting to derive std::iterator, some to use std::iterator_traits, but I don't really understand how to do that. What can possibly go wrong when trying to implement a custom iterator? And what's a simple yet elegant way to do it?
EDIT:
Please don't consider using boost, my question is of more conceptual nature.
EDIT 2:
The FileDB works like this:
rootDir
foo1
bar1
foo1bar1_1.txt
foo1bar1_2.txt
bar2
foo1bar2_1.txt
foo1bar2_2.txt
foo2
fooN
barM
fooNBarM_x.txt
So basicly, I can find a file by its name.
As my container is not in memory, I don't have pointers to its data. So my idea was to store the file's path in the iterator. This way, I can implement operator== with a string comparison, as the paths should be unique. The iterator returned from fileDB.end() would be an empty string and operator* would call fileDB::loadFile() with its filepath.
My biggest concern is about operator++. Having the filename, I can find out the containing directory and search for the next file, but this is really ineffective. Any ideas on how to do that? Or am I completely wrong with my whole concept?
Writing iterators yourself is hardly ever pretty. The pretiest way to add an iterator to your classes is to reuse an existing one. You should be aware, that pointers for example are just as good as iterators in C++ so there are many ways to provide an iterator without actually having to write your own.
This is basically how C++ works in many ways. It tries to make the language expendable and simple for end users by putting a lot of burden on library writers. I.e. library writers can write all the unpretty stuff, so the end user doesn't have to. Iterators are usally a part of a library.
Having said that, here comes the actual ugly part:
To be able to write your own iterators, here are some things you need to be aware of.
Type traits:
Type traits are a simple mechanism to add aditional information to types in C++ which works even with types which cannot be changed themselves. For example for an iterator it is important to know what it iterates over (i.e. the contained type). The way to get this information for a given iterator depends a lot on the iterator. For iterators which actually are objects, you can add typedefs in the class and use those, but for iterators which are pointers you need to infer it from the pointer type. To make this possible the information is stored in a type trait instead so there is a single place an code can look this information. This is the std::iterator_traits type trait.
std::iterator_traits work on anything, which is derived from the std::iterator template as well as on any kind of pointer, without any tweaking. So often it is best to use std::iterator as a base to avoid having to write your own traits specialisation. In case you cannot do this, it is still possible to provide the necessary traits, but it will be harder.
Tag classes and iterator types:
There are several different types of iterators available in C++ which have different behaviour and can/cannot do a lot of different things. Have a look at http://cplusplus.com/reference/std/iterator/ to see what kind of iterators are available and what they can do. The diagrams are not meant to be in an object oriented way (i.e. an input_iterator is neither a sub nor a base class of a forward_iterator), but rather as an API kind of derivation. I.e. you can use all algorithms which were written for an input iterator also with an forward iterator. The table on the page will tell you which methods you have to provide for each category.
Since these categories are not actually sub classes of each other (they shouldn't be, especially when comming from different types of collections), another mechanism is used to identify the capabilities of each iterator. An empty tag class is also included in the std::iterator_traits describing each iterator, which tells what this iterator can do and what it cannot do. If you do not write your own traits, you need to supply this tag class to the std::iterator template upon instantiation.
Example:
This example is taken from the cplusplus.com section on iterators:
class myiterator : public iterator<input_iterator_tag, int>
{
int* p;
public:
myiterator(int* x) :p(x) {}
myiterator(const myiterator& mit) : p(mit.p) {}
myiterator& operator++() {++p;return *this;}
myiterator operator++(int) {myiterator tmp(*this); operator++(); return tmp;}
bool operator==(const myiterator& rhs) {return p==rhs.p;}
bool operator!=(const myiterator& rhs) {return p!=rhs.p;}
int& operator*() {return *p;}
};
This iterator does not really make sense, since it only wraps a pointer, which could also have been used directly. However it can serve as an explanation. The iterator is derived from std::iterator as an input_iterator by supplying the appropriate tag. Also the template is told, that this iterator is iterating over ints. All other types, which are needed difference_type, reference, poiner etc. are automatically infered by the template. In some cases it may make sense to change some of these types manually (for example a std::shared_ptr has to be used as a pointer sometimes). Also the traits needed for this iterator will exist automatically, since it is already derived from std::iterator and the std::iterator_traits know where to find all necessary information.
class FileDB
{
class iterator;
public:
FileDB(std::string dir) : m_rootDir(dir) { m_files = getFileTreeList(); }
void loadFile(std::string filename, File &file) const;
void saveFile(std::string filename, const File &file) const;
iterator begin()
{
return iterator(m_files.begin(), *this);
}
iterator end()
{
return iterator(m_files.end(), *this);
}
private:
std::list<std::string> getFileTreeList() const;
private:
std::string m_rootDir;
std::list<std::string> m_files;
}
class FileDB::iterator
{
public:
iterator(std::list<std::string>::iterator pos, FileDB& owner) : m_pos(pos), m_owner(owner){}
bool operator==(const iterator& rhs) {return m_pos == rhs.m_pos;}
bool operator!=(const iterator& rhs) {return m_pos != rhs.m_pos;}
//etc
void operator++() {++m_pos;}
File operator*()
{
return openFile(*m_pos); // for example open some file descriptor
}
~iterator()
{
closeFile(*m_pos); // clean up!
}
private:
std::list<std::string>::iterator& m_pos;
FileDB& m_owner;
};
Here is iterator which calculatetes subnodes during traversal.
I wrote it for windows, but I think it is not difficult to castomize it for other platforms.
#include <list>
#include <windows.h>
#include <assert.h>
#include <iostream>
#include <string>
class File{};
class Iterator
{
public:
virtual bool isDone() = 0;
virtual void next() = 0;
virtual std::string getFileName() = 0;
virtual ~Iterator(){};
};
bool operator== (Iterator& lhs, Iterator& rhs);
class EndIterator : public Iterator
{
public:
virtual bool isDone() {return true;}
virtual void next(){};
virtual std::string getFileName() {return "end";};
};
class DirectoryIterator : public Iterator
{
public:
DirectoryIterator(const std::string& path);
virtual bool isDone();
virtual void next();
virtual std::string getFileName();
virtual ~DirectoryIterator();
private:
std::list<Iterator*> getSubelementsList(const std::string& path) const;
void init();
private:
bool m_wasInit;
std::string m_path;
std::list<Iterator*> m_leaves;
std::list<Iterator*>::iterator m_current;
};
class FilesIterator : public Iterator
{
public:
FilesIterator(const std::string& fileName);
virtual bool isDone(){return true;};
virtual void next(){};
virtual std::string getFileName();
virtual ~FilesIterator(){};
private:
std::string m_fileName;
};
class DbItertor
{
public:
DbItertor(Iterator* iterator) : m_ptr(iterator){}
DbItertor(const DbItertor& rhs) {*m_ptr = *rhs.m_ptr;}
std::string operator*()
{
if(m_ptr->isDone())
return "end";
return m_ptr->getFileName();
}
//File operator->(){return FileOpen(m_ptr->getFileName());}
void operator++() {m_ptr->next();}
~DbItertor(){delete m_ptr;}
private:
Iterator* m_ptr;
};
class FileDB
{
public:
FileDB(std::string dir) : m_rootDir(dir){}
DbItertor begin()
{
return DbItertor(new DirectoryIterator(m_rootDir));
}
DbItertor end()
{
return DbItertor(new EndIterator());
}
private:
std::string m_rootDir;
};
FilesIterator::FilesIterator(const std::string& fileName) :
m_fileName(fileName)
{}
std::string FilesIterator::getFileName()
{
return m_fileName;
}
DirectoryIterator::DirectoryIterator(const std::string& path) :
m_wasInit(false),
m_path(path)
{}
void DirectoryIterator::init()
{
m_leaves = getSubelementsList(m_path);
m_current = m_leaves.begin();
m_wasInit = true;
next();
}
DirectoryIterator::~DirectoryIterator()
{
for(std::list<Iterator*>::iterator i = m_leaves.begin(); i != m_leaves.end(); ++i)
delete *i;
}
void DirectoryIterator::next()
{
if(!m_wasInit)
init();
if(isDone())
return;
if((*m_current)->isDone())
++m_current;
else
(*m_current)->next();
}
bool DirectoryIterator::isDone()
{
if(!m_wasInit)
init();
return (m_leaves.size() == 0) || (m_current == --m_leaves.end());
}
std::string DirectoryIterator::getFileName()
{
if(!m_wasInit)
init();
return (*m_current)->getFileName();
}
std::list<Iterator*> DirectoryIterator::getSubelementsList(const std::string& path) const
{
std::list<Iterator*> result;
WIN32_FIND_DATA fdFile;
HANDLE hFind = NULL;
char sPath[2048] = {0};
sprintf(sPath, "%s\\*.*", path.c_str());
hFind = FindFirstFile(sPath, &fdFile);
assert(hFind != INVALID_HANDLE_VALUE);
do
{
if(strcmp(fdFile.cFileName, ".") != 0 && strcmp(fdFile.cFileName, "..") != 0)
{
std::string fullName = path;
fullName += std::string(fdFile.cFileName);
if(fdFile.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)
{
fullName += "\\";
result.push_back(new DirectoryIterator(fullName));
}
else
{
result.push_back(new FilesIterator(fullName));
}
}
}
while(FindNextFile(hFind, &fdFile));
FindClose(hFind);
return result;
}
bool operator== (Iterator& lhs, Iterator& rhs)
{
return lhs.getFileName() == rhs.getFileName();
}
bool operator!= (DbItertor& lhs, DbItertor& rhs)
{
return *lhs != *rhs;
}
int main()
{
FileDB db("C:\\456\\");
for(DbItertor i = db.begin(); i != db.end(); ++i)
{
std::cout << *i << std::endl;
}
return 0;
}

Type erasure and interoperability: virtual binary operator issues in C++

I have problems comparing template implementations of the same interface through overriden equality operator.
Interface* ptr1 = ...; Interface* ptr2 = ...;
*ptr1 == *ptr2;
The only solution I've came up to is to enforce that only identically implemented objects are to be compared and to implement comparison like this:
class Interface {
public:
virtual ~Interface() {}
virtual bool operator==(const Interface&) const = 0;
};
template <typename T> class Impl : public Interface {
public:
bool operator==(const Interface& rhs) const {
assert(typeid(rhs) == typeid(const Impl&));
const Impl& rhsRef = static_cast<const Impl&>(rhs);
// ...
}
};
The problem in this solution is that it's too limited for my purposes - I'd want to be able to compare different implementations. If there were a limited number of implementations, it would be possible to use double dispatch pattern. But in my case Impl is a template, so double dispatch is not possible, because it would need a virtual function template:
// This obviously doesn't work.
class Interface {
public:
virtual ~Interface() {}
virtual bool operator==(const Interface&) const = 0;
template <typename T2> virtual bool operator==(const Impl<T2>&) const = 0;
};
template <typename T> class Impl : public Interface {
public:
bool operator==(const Interface& rhs) const {
return rhs == *this;
}
template <typename T2> bool operator==(const Impl<T2>& rhs) const {
// ...
}
};
Is there any solution? I need this to write AnyIterator class, which can wrap any STL iterator. But I can't compare AnyIterators, if they are wrapped around different types, for example iterator and const_iterator:
std::list<int>::iterator it1 = ...; std::list<int>::const_iterator it2 = ...;
AnyIterator<const int> myIter1 = it1; AnyIterator<const int> myIter2 = it2;
it1 == it2; // This works perfectly.
myIter1 == myIter2; // This doesn't work!
I think the problem here is that having operator== in your Interface, just doesn't make any sense at all. If you want to provide comparison for your implementation, that's another matter, like:
bool operator==(const Impl<T>& other) const {
// ...
}
Even for that case, though, I would generally discourage creating operator overloads; instead, provide accessors to get the relevant attributes that someone might want to compare, and leave it up to whoever is using your code to create the comparisons that they want to make.
Do you have a specific use case for these arbitrary comparisons?
You can use dynamic_cast instead of static_cast and check for std::bad_cast (and just always return false in that case). You could use a pointer dynamic_cast instead of a reference cast, in which case you'd just have to check for NULL instead of catching an exception.

Random access priority queue

Continuing List to priority queue
I'm implementing a improved priority_queue with random access.
template <class T, class Container = std::vector<T> >
class Heap {
public:
Heap() {}
Heap(const Container& container) {
container_ = container;
std::make_heap(container_.begin(), container_.end());
}
Heap<T, Container>& operator=(const Heap<T, Container>& heap) {
if (this != &heap)
container_ = heap.container_;
return *this;
}
void push(const T& x) {
container_.push_back(x);
std::push_heap(container_.begin(), container_.end());
}
void pop() {
std::pop_heap(container_.begin(), container_.end());
container_.pop_back();
}
const T& top() {
return container_.front();
}
const Container& getContainer() const {
return container_;
}
T& operator[](size_t n) {
return container_[n];
}
typename Container::const_iterator begin() const {
return container_.begin();
}
typename Container::const_iterator end() const {
return container_.end();
}
size_t size() const {
return container_.size();
}
T& base() {
return container_.back();
}
Container::iterator erase(Container::iterator position) {
return container_.erase(position);
}
private:
Container container_;
};
Am I taking the right way?
Fixed the unary constructor.
Improved code.
Doesn't look that great to me:
The unary constructor should take argument by const reference.
The assignment operator doesn't check for self-assignment.
The getContainer() method shows a lack of clarity in the interface - why would you simply expose the implementation detail like that?
Most importantly: why do you want a "random access priority queue"?
Your pop() method can violate the heap ordering. Use pop_heap() instead of pop_back(). I can't seem to find any other bug right now.
You can easily test such an implementation by pushing in a random integers and pop() them one by one. You should get them back in sorted order. This is known as heap sort.
Suggestions:
Instead of returning a ref to the container you could implement an const iterator to this class.
Note that you should not change the key of the randomly accessed element because it may destroy the heap structure. If you need such functionality you should implement a change_key function which would change the key safely and maintain the heap ordering.

C++ Iterators and inheritance

Have a quick question about what would be the best way to implement iterators in the following:
Say I have a templated base class 'List' and two subclasses "ListImpl1" and "ListImpl2". The basic requirement of the base class is to be iterable i.e. I can do:
for(List<T>::iterator it = list->begin(); it != list->end(); it++){
...
}
I also want to allow iterator addition e.g.:
for(List<T>::iterator it = list->begin()+5; it != list->end(); it++){
...
}
So the problem is that the implementation of the iterator for ListImpl1 will be different to that for ListImpl2. I got around this by using a wrapper ListIterator containing a pointer to a ListIteratorImpl with subclasses ListIteratorImpl2 and ListIteratorImpl2, but it's all getting pretty messy, especially when you need to implement operator+ in the ListIterator.
Any thoughts on a better design to get around these issues?
If you can get away with making List<T>::iterator non-virtual, then delegating the virtualness off add to List makes things simple:
template<typename T>
class List
{
virtual void add_assign(iterator& left, int right) = 0;
public:
class iterator
{
const List* list;
const T* item;
public:
iterator(const List* list, const T* item) : list(list), item(item) {}
iterator& operator +=(int right)
{
list->add_assign(*this, right);
return *this;
}
static iterator operator +(iterator const& left, int right)
{
iterator result = left;
result += right;
return result;
}
};
virtual iterator begin() const = 0;
virtual iterator end() const = 0;
};
Otherwise (if the iterators need to store significantly different data, for example), then you have to do the regular, boring pointer-to-implementation to get your virtualness:
template<typename T>
class List
{
class ItImpl
{
virtual ItImpl* clone() = 0;
virtual void increment() = 0;
virtual void add(int right) = 0;
};
public:
class iterator
{
ItImpl* impl;
public:
// Boring memory management stuff.
iterator() : impl() {}
iterator(ItImpl* impl) : impl(impl) {}
iterator(iterator const& right) : impl(right.impl->clone()) {}
~iterator() { delete impl; }
iterator& operator=(iterator const& right)
{
delete impl;
impl = right.impl->clone();
return *this;
}
// forward operators to virtual calls through impl.
iterator& operator+=(int right)
{
impl->add(right);
return *this;
}
iterator& operator++()
{
impl->increment();
return *this;
}
};
};
template<typename T>
static List<T>::iterator operator+(List<T>::iterator const& left, int right)
{
List<T>::iterator result = left;
result += right;
return result;
}
template<typename T>
class MagicList : public List<T>
{
class MagicItImpl : public ItImpl
{
const MagicList* list;
const magic* the_magic;
// implement ...
};
public:
iterator begin() const { return iterator(new MagicItImpl(this, begin_magic)); }
iterator end() const { return iterator(new MagicItImpl(this, end_magic)); }
};
There is something very important among iterators, called Iterator Category:
InputIterator
OutputIterator
ForwardIterator
BidirectionalIterator
RandomAccessIterator
Each category define an exact set of operations that are supported, efficiently, by the iterator.
Here, it seems you wish to turn down that powerful identification mechanism to create some kind of bastard category in which the operations are all present, but no guarantee is made on their efficiency.
I think your design smells.
So the problem is that the
implementation of the iterator for
ListImpl1 will be different to that
for ListImpl2. I got around this by
using a wrapper ListIterator
containing a pointer to a
ListIteratorImpl with subclasses
ListIteratorImpl2 and
ListIteratorImpl2, but it's all
getting pretty messy, especially when
you need to implement operator+ in the
ListIterator.
This design is fine IMHO, I can't see anything messy about it. Except for equality and subtraction, operations of the iterator can be implemented by virtual function pretty easily, so you'll have something like
class ListIteratorInterface // abstract
{
protected:
virtual Data& operator*()=0;
// and other operations
};
class ListIteratorA;
class ListIteratorB; // implementation of the above
class ListIterator
{
ListIteratorInterface* impl_;
public:
// when you create this, allocate impl_ on the heap
// operations, forward anything to impl_
};
You could store operator+ as a private virtual method in the base class, and have the iterator call that.
Alternatively, you could consider statically polymorphic list classes, rather than runtime polymorphism.
Say I have a templated base class 'List' and two subclasses "ListImpl1" and "ListImpl2"
What exactly do you gain by using inheritance here?