nyarlathotep108
nyarlathotep108

Reputation: 5523

Iterator over pure virtual container interface in C++

I have a pure virtual interface to a container which is more or less like this:

class IContainer
{
public:
    virtual ~IContainer() = default;
    virtual Element& operator[](size_t index) = 0;
    virtual const Element& operator[](size_t index) const = 0;
    virtual size_t size() const = 0;
};

I would like to make use of range for loops, so I need to define begin() and end(). In order to do so, I need to define the iterator type as well.

It should be not be particularly hard, but nevertheless I would like to know if is there already anything in STL or Boost that can come to help, before I start coding something that already exists.

Upvotes: 5

Views: 717

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

It might not be a good idea, but adding for(:) loop support is relatively easy here. I'll be minimal.

I'll create an iteroid, a not-iterator that is enough to support for(:) loops. This requires ++, != and unary * support, and nothing else.

template<class C>
struct index_iteroid {
  decltype(auto) operator*()const {
    return (*container)[i];
  }
  index_iteroid(index_iteroid const&)=default;
  index_iteroid& operator=(index_iteroid const&)=default;
  friend bool operator==(index_iteroid const& lhs, index_iteroid const& rhs) {
    return std::tie(lhs.i, lhs.container)==std::tie(rhs.i, rhs.container);
  }
  friend bool operator!=(index_iteroid const& lhs, index_iteroid const& rhs) {
    return !(lhs==rhs);
  }
  void operator++()&{
    ++i;
  }
  index_iteroid(C* c, std::size_t in):i(in), container(c) {}
private:
  std::size_t i = 0;
  C* container = nullptr;
};

now we use it:

class IContainer
{
public:
    virtual ~IContainer() = default;
    virtual Element& operator[](size_t index) = 0;
    virtual const Element& operator[](size_t index) const = 0;
    virtual size_t size() const = 0;
    index_iteroid<IContainer> begin() { return {this, 0}; }
    index_iteroid<IContainer> end() { return {this, size()}; }
    index_iteroid<IContainer const> begin() const { return {this, 0}; }
    index_iteroid<IContainer const> end() const { return {this, size()}; }
};

and there you have it.

void test( IContainer* cont ) {
  if (!cont) return;
  for(Element& e : *cont) {
    // code
  }
}

please excuse any typos.

Now a full iterator takes about 2-3 times as much code as my iteroid does, but nothing tricky, just annoying boilerplate mostly.


The standard doesn't have much to help you. For boost, you could compose a counting iterator with a function caling iterator/generator, and have the function call use []. Boost also has some utilities to make it take less boilerplate to write a full iterator, if you want to upgrade the iteroid to an iterator.

Upvotes: 3

Caleth
Caleth

Reputation: 62686

C++ doesn't do "interfaces" like this. The idiomatic way is for the (potential) clients of IContainer to instead be templated over the container type and just call values[index], or be templated over an iterator type and call like *(first + offset).

In C++20 you will be able to write a Container Concept, which behaves somewhat like an interface definition, but you can already express a Concept as documented requirements.

If instead you want a type-erased random access "container", you can use boost::any_range<Element, boost::random_access_traversal_tag>

Upvotes: 1

Related Questions