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.

+3


source to share


1 answer


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 uses decltype(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 (qualify begin()

    and end()

    , use static_cast<const D*>

    in CRTP, declare option contain const_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.
+1


source







All Articles