Overall design blended with a curiously repeating pattern. C ++
Let's consider this problem. I have a class Base
and three classes derived from Base
. For example: DerivedA
, DerivedB
and DerivedC
. Each derived class has its own unique container. Therefore it DerivedA
has std::vector<int>
, DerivedB
has std::set<int>
and DerivedC
has std::map<int, std::string>
. And I want the interface in to Base
have access to the container of the derived class it points to.
Base* d1 = new DerivedA;
for(std::vector<int>::iterator iter = d1->begin(); iter != d1->end(); ++iter)
{
//processing
}
I tried to wrap each container for class separation and store a pointer to their base in 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'm kind of lost in this awful design.
source to share
To do what you want, you need to define a generic iterator type that can be returned from different overrides begin()
and end()
in derived classes.
Before that, of course, you need to decide what exactly you want this iterator to do, as Jakk explained in his comment. First, you need to decide what value_type
will be caused by the indirection through such an iterator. The only common type I can think of given the three different containers is const int
because keys in std::map
are const
and std::set
iterators are const
iterators (since the elements themselves are keys). So, when iterating using a generic iterator type, you will only be able to observe int
.
Implementing the iterator now requires calling different code (at runtime) depending on the derived class from which it was generated. This is a typical use case for type erasure. When done correctly, it will allow you to wrap any iterator as long as it supports the required interface. In your case, however, you may not need to go that far, as I assume you know the full set of containers you need to support, so the set of iterator types is well known and limited.
This means that you can use boost::variant
to store a wrapped iterator. This should be more efficient than a full type erasure solution as it avoids some internal virtual function calls and possibly some heap allocations (unless the type erasure solution can use some small object optimization, which is perfectly possible for iterators, but even harder to implement).
This is where the skeleton of such an iterator is implemented, along with the class hierarchy that uses it, and some simple test code. Note that I only used the basic iterator functions required for your loop to work.
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include "boost/variant.hpp"
//Helper function object types to implement each operator on the variant iterator.
struct indirection_visitor : boost::static_visitor<const int&>
{
const int& operator()(std::vector<int>::iterator i) const { return *i; }
const int& operator()(std::set<int>::iterator i) const { return *i; }
const int& operator()(std::map<int, std::string>::iterator i) const { return i->first; }
};
struct prefix_increment_visitor : boost::static_visitor<>
{
template<typename I> void operator()(I& i) const { ++i; }
};
//The iterator itself.
//It should probably hide the internal variant, in which case the non-member operators
//should be declared as friends.
struct var_iterator : std::iterator<std::bidirectional_iterator_tag, const int>
{
var_iterator() { }
template<typename I> var_iterator(I i) : it(i) { }
boost::variant<std::vector<int>::iterator, std::set<int>::iterator, std::map<int, std::string>::iterator> it;
const int& operator*() { return boost::apply_visitor(indirection_visitor(), it); }
var_iterator& operator++()
{
boost::apply_visitor(prefix_increment_visitor(), it);
return *this;
}
};
inline bool operator==(var_iterator i1, var_iterator i2) { return i1.it == i2.it; }
inline bool operator!=(var_iterator i1, var_iterator i2) { return !(i1 == i2); }
//Here the class hierarchy.
//We use CRTP only to avoid copying and pasting the begin() and end() overrides for each derived class.
struct Base
{
virtual var_iterator begin() = 0;
virtual var_iterator end() = 0;
};
template<typename D> struct Base_container : Base
{
var_iterator begin() override { return static_cast<D*>(this)->container.begin(); }
var_iterator end() override { return static_cast<D*>(this)->container.end(); }
};
struct DerivedA : Base_container<DerivedA>
{
std::vector<int> container;
};
struct DerivedB : Base_container<DerivedB>
{
std::set<int> container;
};
struct DerivedC : Base_container<DerivedC>
{
std::map<int, std::string> container;
};
//Quick test.
void f(Base* bp)
{
for(auto iter = bp->begin(); iter != bp->end(); ++iter)
{
std::cout << *iter << ' ';
}
std::cout << '\n';
//We have enough to make range-based for work too.
for(auto i : *bp)
std::cout << i << ' ';
std::cout << '\n';
}
int main()
{
DerivedA da;
da.container = {1, 2, 3};
f(&da);
DerivedB db;
db.container = {4, 5, 6};
f(&db);
DerivedC dc;
dc.container = std::map<int, std::string>{{7, "seven"}, {8, "eight"}, {9, "nine"}};
f(&dc);
}
Implementation Notes:
- As mentioned above, this is not a complete bidirectional iterator; I chose this tag as the most powerful generic iterator among your container types.
- I have compiled and (superficially) tested the code in Clang 3.6.0 and GCC 5.1.0 in C ++ 11 mode, and in Visual C ++ 2013 using boost 1.58.0.
- The code works in C ++ 14 mode as well as in the compilers above (as well as in Visual C ++ 2015 CTP6) but needs a little change due to a bug in boost 1.58 (I have to report this), otherwise you get an ambiguity error. You need to remove the base class
indirection_visitor
and set the return type for this visitor automatically. This only works in C ++ 14 as it usesdecltype(auto)
internally and this new code is causing ambiguity. Earlier versions of boost don't have this problem, but they also don't have automatic return types. - In C ++ 14 mode and boost 1.58, you can use generic lambdas to implement simple visitors like
prefix_increment_visitor
making the code simpler. - I removed the comparison visitors from my first version of the code as it
boost::variant
already provides the default equality operator and that's enough for this case (the example is long enough as it is). - You can add
const
if (qualifybegin()
andend()
, usestatic_cast<const D*>
in CRTP, declare option containconst_iterator
s, customize visitor) to get the true behavior of the iterator . - You can of course implement some poor man's option and not use boost, but
boost::variant
it makes everything much simpler, cleaner and safer.
source to share