Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adapting non-iterable containers to be iterated via custom templatized iterator

Tags:

c++

templates

I have some classes, which for various reasons out of scope of this discussion, I cannot modify (irrelevant implementation details omitted):

class Foo { /* ... irrelevant public interface ... */ };

class Bar {
  public:
    Foo& get_foo(size_t index) { /* whatever */ }
    size_t size_foo() { /* whatever */ }
};

(There are many similar 'Foo' and 'Bar' classes I'm dealing with, and it's all generated code from elsewhere and stuff I don't want to subclass, etc.)

[Edit: clarification - although there are many similar 'Foo' and 'Bar' classes, it is guaranteed that each "outer" class will have the getter and size methods. Only the getter method name and return type will differ for each "outer", based on whatever it's "inner" contained type is.

So, if I have Baz which contains Quux instances, there will be Quux& Baz::get_quux(size_t index), and size_t Baz::size_quux().]

Given the design of the Bar class, you cannot easily use it in STL algorithms (e.g. for_each, find_if, etc.), and must do imperative loops rather than taking a functional approach (reasons why I prefer the latter is also out of scope for this discussion):

Bar b;
size_t numFoo = b.size_foo();
for (int fooIdx = 0; fooIdx < numFoo; ++fooIdx) {
  Foo& f = b.get_foo(fooIdx);
  /* ... do stuff with 'f' ... */
}

So... I've never created a custom iterator, and after reading various questions/answers on S.O. about iterator_traits and the like, I came up with this (currently half-baked) "solution":

First, the custom iterator mechanism (NOTE: all uses of 'function' and 'bind' are from std::tr1 in MSVC9):

// Iterator mechanism...
template <typename TOuter, typename TInner>
class ContainerIterator : public std::iterator<std::input_iterator_tag, TInner> {
  public:
    typedef function<TInner& (size_t)> func_type;

    ContainerIterator(const ContainerIterator& other) : mFunc(other.mFunc), mIndex(other.mIndex) {}

    ContainerIterator& operator++() { ++mIndex; return *this; }

    bool operator==(const ContainerIterator& other) {
      return ((mFunc.target<TOuter>() == other.mFunc.target<TOuter>()) && (mIndex == other.mIndex));
    }

    bool operator!=(const ContainerIterator& other) { return !(*this == other); }

    TInner& operator*() { return mFunc(mIndex); }

  private:
    template<typename TOuter, typename TInner>
    friend class ContainerProxy;

    ContainerIterator(func_type func, size_t index = 0) : mFunc(func), mIndex(index) {}

    function<TInner& (size_t)> mFunc;
    size_t mIndex;
};

Next, the mechanism by which I get valid iterators representing begin and end of the inner container:

// Proxy(?) to the outer class instance, providing a way to get begin() and end()
// iterators to the inner contained instances...
template <typename TOuter, typename TInner>
class ContainerProxy {
  public:
    typedef function<TInner& (size_t)> access_func_type;
    typedef function<size_t ()> size_func_type;

    typedef ContainerIterator<TOuter, TInner> iter_type;

    ContainerProxy(access_func_type accessFunc, size_func_type sizeFunc) : mAccessFunc(accessFunc), mSizeFunc(sizeFunc) {}

    iter_type begin() const {
      size_t numItems = mSizeFunc();
      if (0 == numItems) return end();
      else return ContainerIterator<TOuter, TInner>(mAccessFunc, 0);
    }
    iter_type end() const {
      size_t numItems = mSizeFunc();
      return ContainerIterator<TOuter, TInner>(mAccessFunc, numItems);
    }

  private:
    access_func_type mAccessFunc;
    size_func_type mSizeFunc;
};

I can use these classes in the following manner:

// Sample function object for taking action on an LMX inner class instance yielded
// by iteration...
template <typename TInner>
class SomeTInnerFunctor {
  public:
    void operator()(const TInner& inner) {
      /* ... whatever ... */
    }
};

// Example of iterating over an outer class instance's inner container...
Bar b; /* assume populated which contained items ... */
ContainerProxy<Bar, Foo> bProxy(
  bind(&Bar::get_foo, b, _1),
  bind(&Bar::size_foo, b));
for_each(bProxy.begin(), bProxy.end(), SomeTInnerFunctor<Foo>());

Empirically, this solution functions correctly (minus any copy/paste or typos I may have introduced when editing the above for brevity).

So, finally, the actual question:

I don't like requiring the use of bind() and _1 placeholders, etcetera by the caller. All they really care about is: outer type, inner type, outer type's method to fetch inner instances, outer type's method to fetch count inner instances.

Is there any way to "hide" the bind in the body of the template classes somehow? I've been unable to find a way to separately supply template parameters for the types and inner methods separately...

Thanks!
David

like image 648
DAldridge Avatar asked Jun 22 '12 23:06

DAldridge


1 Answers

Alternately, you could always take the functions as template parameters themselves, if they have a predictable signature:

template <typename TOuter, typename TInner, 
          TInner& (TOuter::*getfunc)(size_t )>
class ContainerIterator
{
public:
    //...
    TInner& operator*() {return mContainerRef.*getfunc(mIndex);}
    //...
};

template <typename TOuter, typename TInner, 
          size_t (TOuter::*sizefunc)(),
          TInner& (TOuter::*getfunc)(size_t )>
class ContainerProxy
{
public:
    //...
    ContainerIterator<TOuter, TInner, getfunc> end() {
        return ContainerIterator<TOuter, TInner, getfunc>
                   (mContainerRef, 
                    mContainerRef.*sizefunc());
    }
    //...
};

int main()
{
   Bar b;
   ContainerProxy<Bar, Foo, &Bar::size_foo, &Bar::get_foo> proxy(b);
   std::for_each(proxy.begin(), proxy.end(), /*...*/);
}

Or go even further ( http://ideone.com/ulIC7 ) and pass the functions by wrapping them up in std::function:

template <typename TOuter, typename TInner>
struct ContainerIterator : public std::iterator<std::input_iterator_tag, TInner>
{
    TOuter& mContainerRef;
    //...
    typedef std::function<TInner& (TOuter*, size_t)> getfunc_type;
    getfunc_type mGetfunc;

    ContainerIterator(TOuter& containerRef, size_t index, getfunc_type const& getFunc)
    /* ... */ {}
    TInner& operator*() {return mGetfunc(&mContainerRef, mIndex);}
    ContainerIterator<TOuter, TInner>& operator++() {++mIndex; return *this;}

    // ...
};

template <typename TOuter, typename TInner>
struct ContainerProxy
{
    TOuter& mContainerRef;

    typedef std::function<size_t (TOuter*)> sizefunc_type;
    sizefunc_type mSizefunc;

    typedef std::function<TInner& (TOuter*, size_t)> getfunc_type;
    getfunc_type mGetfunc;

    ContainerProxy(TOuter& containerRef, sizefunc_type sizefunc, getfunc_type getfunc)
    // ...
    {
    }

    // ...

    ContainerIterator<TOuter, TInner> end() const
    {
        return ContainerIterator<TOuter, TInner>(mContainerRef,
                                                 mSizefunc(&mContainerRef), 
                                                 mGetfunc);
    }
};

int main()
{
    Bar b=...;

    ContainerProxy<Bar, Foo> proxy(b, &Bar::size_foo, &Bar::get_foo); 
    std::for_each(proxy.begin(), proxy.end(), /*...*/);
}
like image 196
Managu Avatar answered Oct 05 '22 12:10

Managu