Reputation: 99
I want to inherit a iterator for an container class. It should always have the same signature independent of the actual implementation.
But I have the problem that in ContainerImpl
the returned Container::Iterator
from ContainerImpl::begin()
slices the overridden methods from ContainerImpl::Iterator
back to the ones of Container::Iterator
.
How can I force the more specific method to be used? Or can I rewrite my classes so that it behaves like that?
Below is a working example. It compiles at least against C++17.
#include <tuple>
using std::tuple;
class Container {
// some code
public:
class Iterator {
public:
virtual tuple<int, int> &operator*() {};
virtual Iterator &operator++() {};
virtual bool operator!=(const Iterator &rhs) const {};
};
virtual Iterator begin() = 0;
virtual Iterator end() = 0;
};
class ContainerImpl : public Container {
// some code
public:
class Iterator : public Container::Iterator {
public:
Iterator(ContainerImpl &containerImpl) {
// init
}
Iterator() {
// end_iterator
}
virtual tuple<int, int> &operator*() override {
// impl
};
virtual Container::Iterator &operator++() override {
// impl
};
virtual bool operator!=(const Container::Iterator &rhs) const override {
// impl
};
};
virtual Container::Iterator begin() override {
return Iterator{*this};
};
virtual Container::Iterator end() override {
return Iterator{};
};
};
int main() {
ContainerImpl cont{};
for (auto &entry : cont) {
// here Container::Iterator::operator++() is called
// instead of ContainerImpl::Iterator::operator++()
}
}
edit:
Thanks! I read through your comments and figured out that I should try an static approach using templates. Performance is quite important for those iterators. This will need refactoring of other code where I use this code. But it should be moderate.
Here is what I came up with:
#include <tuple>
#include <type_traits>
using std::tuple;
template<class SubContainer>
class Container;
template<class ContImpl>
class Iterator {
static_assert(std::is_base_of<Container<ContImpl>, ContImpl>::value, "ContImpl not derived from Container");
public:
tuple<int, int> &operator*();
Iterator<ContImpl> &operator++();
bool operator!=(const Iterator<ContImpl> &rhs) const;
};
template<class SubContainer>
class Container {
public:
Iterator<SubContainer> begin();
Iterator<SubContainer> end();
};
class ContImpl;
template<>
class Iterator<ContImpl> {
public:
ContImpl &container;
Iterator(ContImpl &container, bool is_end = false) : container(container) { init(); }
void init() {
}
tuple<int, int> &operator*() {
}
Iterator<ContImpl> &operator++() {
}
bool operator!=(const Iterator<ContImpl> &rhs) const {
}
};
class ContImpl : public Container<ContImpl> {
public:
Iterator<ContImpl> begin() {
return Iterator<ContImpl>(*this, false);
}
Iterator<ContImpl> end() {
return Iterator<ContImpl>(*this, true);
}
};
int main() {
ContImpl cont{};
for (auto &entry : cont) {
// Iterator<ContImpl>::operator++() is called.
}
}
Upvotes: 2
Views: 430
Reputation: 39818
If you want to dynamically abstract away the differences between, say, std::list<int>
and std::vector<int>
, then what you want is type erasure. But do note that, using things like template template parameters, it is also possible to statically abstract over containers with similar interfaces (with the same or different element types), which (if it meets your requirements) should be significantly faster.
Regarding your template-based approach: while it might have value as documentation, the methods in Container
are unnecessary: static polymorphism does not need any “base functions” to override. If you remove them, you can do without that CRTP class entirely. Then, similarly, you can replace the Iterator
class template with a simple class ContImpl::iterator
.
Of course, at that point all that’s left is the same interface compatibility as between the standard containers (which allows code like
template<template<class> class C>
C<int> makeInts(/*...*/);
void f() {
auto d=makeInts<std::deque>(/*...*/);
// ...
}
that I suggest above). The interesting exercise is to hide a different container interface behind wrapper classes or with a traits interface.
Upvotes: 1