Consider this kind of problem. I have a Base
class and three classes derived from Base
. For instance: DerivedA
, DerivedB
and DerivedC
. Each derived class has its unique container. Hence DerivedA
has std::vector<int>
, DerivedB
has std::set<int>
and DerivedC
has std::map<int, std::string>
. And I want an interface in Base
to access the container of derived class on which it is currently pointed to.
Base* d1 = new DerivedA;
for(std::vector<int>::iterator iter = d1->begin(); iter != d1->end(); ++iter)
{
//processing
}
I tried to wrap each container to separate class and keep a pointer of their base
in the Base class
.
class CollA;
template<class T>
class traits;
template<>
class traits<CollA>
{
public:
typedef vector<int> container;
};
template<class T>
class Coll
{
public:
typedef typename traits<T>::container container;
typename container::iterator begin() const
{
}
};
class CollA : public Coll<CollA>
{
typedef traits<CollA>::container container;
public:
container::iterator begin()
{
return V.begin();
}
private:
vector<int> V;
};
class Base
{
public:
Base()
{
}
// what to do here? I must keep a pointer to Coll; But Coll itself is a template
};
Suggest me something. I am kind of lost in this horrible design.
In order to do what you want, you need to define a common type of iterator that can be returned from the different
begin()
andend()
overrides in the derived classes.Before that, of course, you need to decide what exactly you want that iterator to do, as Yakk explained in his comment. For starters, you need to decide what
value_type
will result from indirecting through such an iterator. The only common type that I can think of given your three different containers isconst int
, as keys instd::map
s areconst
andstd::set
iterators areconst
iterators (since the elements are keys themselves). So, when iterating using the common iterator type, you'll only be able to observe theint
s in there.Now, the iterator implementation will need to call different code (at runtime) depending on the derived class from which it originated. This is a typical use case for type erasure. When done properly, this would allow you to wrap any kind of iterator, as long as it supports the interface you need. In your case however, you may not need to go that far, since I suppose you know the full set of containers you need to support, so the set of iterator types is well known and bounded as well.
This means you can use a
boost::variant
to store the wrapped iterator. This should be more efficient than a full type erasure solution, since it avoids some internal virtual function calls and possibly some heap allocations (unless the type erasure solution can use some kind of small object optimization, which is fairly possible for iterators, but is even more complicated to implement).Here's a skeleton implementation of such an iterator, together with the class hierarchy using it and some simple test code. Note that I've only implemented the basic iterator functionality that's needed to make your loop work.
Implementation notes:
indirection_visitor
and let the return type of this visitor be determined automatically. This only works in C++14, as it usesdecltype(auto)
internally, and it's this new code that causes the ambiguity. Earlier versions of boost don't have this problem, but don't have autodetection of return types either.prefix_increment_visitor
, which makes the code more straightforward.boost::variant
already provides a default equality operator and it's enough for this case (the example is long enough as it is).const
in the required places to get true const iterator behaviour if needed (qualifybegin()
andend()
, usestatic_cast<const D*>
in CRTP, declare the variant to containconst_iterator
s, adjust the visitor).boost::variant
makes everything much easier, cleaner and safer.