Alfredo Di Napoli Alfredo Di Napoli - 3 months ago 9
C++ Question

C++ : Using different iterator types in subclasses without breaking the inheritance mechanism

I'm trying to achieve the following: Given an abstract class MemoryObject, that every class can inherit from, I have two subclasses: A Buffer and a BigBuffer:

template <typename T>
class MemoryObject
{
public:

typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator;

[...] //Lot of stuff

virtual iterator begin() = 0;
virtual iterator end() = 0;
};


A Buffer:

template <typename T>
class Buffer: public MemoryObject<T>
{
public:
typedef typename std::vector<T>::iterator iterator;
iterator begin() { return buffer_.begin(); }
iterator end() { return buffer_.end(); };

[...] //Lot of stuff

private:
std::vector<T> buffer_;
};


And finally:

template <typename T>
class BigBuffer: public MemoryObject<T>
{
public:
[...] //Omitted, for now

private:
std::vector<Buffer<T>*> chunks_;
};


As you can see, a BigBuffer holds a std::vector of Buffer<T>*, so you can view a BigBuffer as an aggregation of Buffer(s). Futhermore, I have a bunch of functions that must work of every MemoryObject, so this is a real signature:

template <class KernelType, typename T>
void fill(CommandQueue<KernelType>& queue, MemoryObject<T>& obj, const T& value)
{
//Do something with obj
}


What's the point? - You may ask. The point is that I must implement iterators over these classes. I've already implemented them for Buffer, and is exactly what I need: be able to iterate over a Buffer, and access to ranges (for example b.begin(), b.begin() + 50).
Obviously I can't do the same for BigBuffer, because the real data (that is inside each Buffer' private variable buffer_) is scattered accross the memory. So I need a new class, let's call it BigBufferIterator, which can overload operator like * or +, allowing me to "jump" from a memory chunk to another without incurring in in segmentation fault.

The problems are two:


  1. The iterator type of MemoryObject is different from the iterator
    type of BigBuffer: the former is a std::vector<T>::iterator, the
    latter a BigBufferIterator. My compiler obviously complains

  2. I want be able to preserve the genericity of my functions signatures
    passing to them only a MemoryObject<T>&, not specializing them for
    each class type.



I've tried to solve the first problem adding a template parameter classed Iterator, and giving it a default argument to each class, with a model loosely based to Alexandrescu's policy-based model. This solution solved the first issue, but not the second: my compiled still complains, telling me: "Not known conversion from BigBuffer to MemoryObject", when I try to pass a BigBuffer to a function (for example, the fill() ). This is because Iterator types are different..

I'm really sorry for this poem, but It was the only way to proper present my problem to you. I don't know why someone would even bother in reading all this stuff, but I'll take pot luck.

Thanks in advance, only just for having read till this point.

Humbly,
Alfredo

Answer

They way to go is to use the most general definition as the iterator type of the base. That is, you want to treat the data in a Buffer as just one segment while the BigBuffer is a sequence of the corresponding segments. This is a bit unfortunate because it means that you treat your iterator for the single buffer in Buffer as if it may be multiple buffers, i.e. you have a segmented structure with just one segment. However, compared to the alternative (i.e. a hierarchy of iterators with virtual functions wrapped by a handle giving value semantics to this mess) you are actually not paying to bad a cost.

Comments